你是否也曾在 Java 开发中陷入这样的困境:手动管理对象依赖时,代码里满是繁琐的new关键字,一旦需求变动,牵一发而动全身;想要实现事务控制,又得在业务逻辑中嵌入大量重复的事务处理代码,既臃肿又难以维护?如果你对这些场景深有体会,那么你一定听说过甚至正在使用一个改变了 Java 开发格局的框架 ------Spring。从 2003 年首次发布至今,Spring 早已不是简单的 "工具类集合",它以 "简化开发、降低耦合" 为核心,构建起了一套覆盖从后端服务到前端交互、从单体应用到微服务架构的完整生态,成为全球数百万 Java 开发者的 "标配"。今天,我们就一起走进 Spring 的世界,看看这个 "开发神器" 究竟如何解决我们的痛点,又为何能在二十多年的技术迭代中始终保持活力,尤其聚焦其核心思想之一 ------IOC(控制反转)的魅力。
一、核心概念
1,IOC(Inversion of Control)控制反转
使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
2,Spring技术对IOC思想进行了实现
Spring提供了一个容器,称为IOC容器,用来充当IOC思想的"外部"。
IOC容器负责对象的创建,初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为Bean。
3,DI(Dependency Injection)依赖注入
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入。

二、基于XML配置的Spring应用
1,Bean的常用配置概览

2,SpringBean的配置详解
1,Bean的基础配置
例如:配置UserDaoImpl由Spring容器负责管理
<bean id="userDao" class="com.Dao.Impl.UserDaoImpl"/>
此时存储到Spring容器(singleObjects单例池)中的Bean的beanName是userDao,值是UserDaoImpl对象,可以根据beanName获取Bean实例
applicationContext.getBean("userDao");
如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName
2,Bean的别名配置(少用)
可以为当前Bean指定多个别名,根据别名也可以获得Bean对象
<bean id="userDao" name="aaa,bbb" class="com.mxw.Dao.Impl.UserDaoImpl"/>
此时多个名称都可以获得UserDaoImpl实例对象
userDao userdao = (userDao) applicationContext.getBean("userDao"); userDao userdao1 = (userDao) applicationContext.getBean("aaa"); userDao userdao2 = (userDao) applicationContext.getBean("bbb");
3,Bean的范围配置------scope
默认情况下,单纯的Spring环境Bean的作用范围有两个:Singleton和Prototype
singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。(不会存储到容器内部的单例池中)
4,Bean的延迟加载
当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等到用到时在创建Bean实例并存储到单例池中去,后续再使用该Bean直接从单例池获取即可,本质上该Bean还是单例的。
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl" lazy-init="true"/>
5,Bean的初始化和销毁方法配置(不常用)
Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过以下配置:
// xml配置内容 <bean id="userDao" class="com.Dao.Impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>
// 测试类内容 //ApplicationContext类中没有关闭方法,需要使用ClassPathXmlApplicationContext类 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml"); userDao userdao = (userDao) applicationContext.getBean("userDao"); // 显式关闭 applicationContext.close();
扩展:除此之外,我们还可以通过实现InitializingBean接口,完成一些Bean的初始化操作。
6,Bean的实例化配置
Spring的实例化方式主要如下两种:
1)构造方式实例化:底层通过构造方法对Bean进行实例化
构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的几乎都是无参构造方式,以下是有参构造方式实例化Bean:
public UserDaoImpl(String name){System.out.println(name);}
有参构造在实例化Bean时,需要参数的注入,通过标签,嵌入在标签内部提供构造参数,如下:
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"> <!-- "name"表示参数名 "haohao"表示给参数赋的值 --> <constructor-arg name="name" value="haohao"/> </bean>
工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化,又分为如下三种:
1,静态工厂方法实例化Bean
// 无参数版
// factory类中内容
public class MyBeanFactory {
public static userDao userDao(){
// 利用工厂方法创建对象的好处:
// Bean创建之前可以进行一些其他业务逻辑操作
return new UserDaoImpl();
}
}
// xml文件内容
<bean id="userDao1" class="com.mxw.factory.MyBeanFactory" factory-method="userDao"></bean>
// 有参数版
// factory类中内容
public class MyBeanFactory {
public static userDao userDao(String name,int age){
return new UserDaoImpl();
}
}
// xml文件内容
<bean id="userDao1" class="com.mxw.factory.MyBeanFactory" factory-method="userDao">
<constructor-arg name="name" value="haohao"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
</bean>
2,实例工厂方法实例化Bean
java
// 无参数版
// factory类中内容
public class MyBeanFactory2 {
public userDao userDao(){
return new UserDaoImpl();
}
}
// xml文件内容
// 实例方法需要现有实例对象才能调用 1,配置工厂对象 2,配置工厂方法
<!-- 配置工厂对象 -->
<bean id="myBeanFactory2" class="com.mxw.factory.MyBeanFactory2"></bean>
<bean id="userDao2" factory-bean="myBeanFactory2" factory-method="userDao"></bean>
// 有参数版
// factory类中内容
public class MyBeanFactory2 {
public userDao userDao(String username){
return new UserDaoImpl();
}
}
// xml文件内容
<!-- 配置工厂对象 -->
<bean id="myBeanFactory2" class="com.mxw.factory.MyBeanFactory2"></bean>
<bean id="userDao2" factory-bean="myBeanFactory2" factory-method="userDao">
<constructor-arg name="username" value="tom"></constructor-arg>
</bean>
注意:只要是产生bean对象的方法内部参数配置都可以使用标签
3,实现FactoryBean规范延迟实例化Bean
java
// factory类中内容
public class MyBeanFactory3 implements FactoryBean<userDao> {
@Override
public userDao getObject() throws Exception {
return new UserDaoImpl();
}
@Override
public Class<?> getObjectType() {
return userDao.class;
}
}
// xml文件内容
// 不用配置工厂方法,由于是一种规范,所以默认调用getObject方法
<bean id="userDao3" class="com.mxw.factory.MyBeanFactory3"></bean>
注意:对象不像之前一样保存在beanFactory中的signaltonObjects中(signaltonObjects中保存的是MyBeanFactory3),而是保存在beanFactory中的factoryBeanObjectCache中。并且是在调用了getbean方法时才调用getObject方法返回对象。(用到的时候产生bean对象)
7,Bean的依赖注入配置
Bean的依赖注入有两种方式:

其中,ref是reference的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id。value用于注入普通属性值。
依赖注入的数据类型有如下三种:
1)普通数据类型,例如:String,int,boolean等,通过value属性指定。
2)引用数据类型,例如:UserDaoImpl,DataSource等,通过ref属性指定。
3)集合数据类型,例如:List,Map,Properties等。
java
// userServiceImpl类中内容
public class userServiceImpl implements userService {
// 注入List
private List<String> stringList;
private List<userDao> daoList;
public void setStringList(List<String> stringList) {
this.stringList = stringList;
}
public void setDaoList(List<userDao> daoList) {
this.daoList = daoList;
}
}
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl">
<property name="stringList">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<property name="daoList">
<list>
<!-- 方式一 -->
<bean class="com.mxw.Dao.Impl.UserDaoImpl"></bean>
<!-- 方式二 -->
<ref bean="userDao"></ref>
</list>
</property>
</bean>
<!-- 该处bean又上面引用 -->
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>
// userServiceImpl类中内容
public class userServiceImpl implements userService {
// 注入set
private Set<String> stringSet;
private Set<userDao> daoSet;
public void setDaoSet(Set<userDao> daoSet) {
this.daoSet = daoSet;
}
public void setStringSet(Set<String> stringSet) {
this.stringSet = stringSet;
}
}
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl">
<property name="stringSet">
<set>
<value>1</value>
<value>2</value>
<value>3</value>
</set>
</property>
<property name="daoSet">
<set>
<!-- 注入set的两种方式和注入list的两种方式一样,这里只写第一种 -->
<bean class="com.mxw.Dao.Impl.UserDaoImpl"></bean>
</set>
</property>
<!--Map注入-->
<property name="objects">
<map>
<entry key="张明" value="1"></entry>
<entry key="李白" value="2"></entry>
<entry key="王伟" value="3"></entry>
</map>
</property>
</bean>
// userServiceImpl类中内容
public class userServiceImpl implements userService {
// 注入properties
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public void show() {
System.out.println(properties);
}
}
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl">
<property name="properties">
<props>
<prop key="p1">ppp</prop>
<prop key="p2">ppp</prop>
</props>
</property>
</bean>
扩展:自动装配方式
如果被注入的属性类型是Bean引用的话,那么可以在标签中使用autowire属性去配置自动注入方式,属性值有两个:
byName:通过属性名自动装配,即去匹配setXxx与id="xxx"(name="xxx")是否一致;
byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错。
java
// 1,byName
// userServiceImpl类中内容
public class userServiceImpl implements userService {
private userDao userDao;
public void setUserDao(userDao userDao) {
this.userDao = userDao;
}
}
// xml文件内容
// 根据id="userDao"的beanName和setUserDao的"UserDao"一样,所以将第二个bean对象注入到第一个bean对象中。
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl" autowire="byName"></bean>
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>
// 1,byType
// userServiceImpl类中内容不变
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl" autowire="byType"></bean>
// 如果设置多个同类型的bean会报错(报错内容:No qualifying bean of type 'com.mxw.Dao.userDao' available: expected single matching bean but found 2: userDao,userDao1)
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>
<bean id="userDao1" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>
8,Spring的其他配置标签
Spring的xml标签大体上分为两类,一种是默认标签,一种是自定义标签
默认标签:就是不用额外导入其他命名空间约束的标签,例如标签
Spring的默认标签用到的是Spring的默认命名空间

该命名空间约束下的默认标签如下:

自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如标签
标签,除了经常用的作为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境
java
<!-- 配置测试环境下,需要加载的Bean实例-->
<beans profile="test"></beans>
<!-- 配置开发环境下,需要加载的Bean实例-->
<beans profile="dev"></beans>
可以使用以下两种方式指定被激活的环境:
使用命令行动态参数,虚拟机参数未知加载-Dspring.profiles.active=test
使用代码的方式设置环境变量System.setProperty("spring.profiles.active","test")
java
// 测试类中内容
// 指定开发环境 (如果指定了环境那么只有环境里面的和公共部分的生效,如果没有指定环境那么只有公共环境的生效)
System.setProperty("spring.profiles.active","dev");
<import>标签,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务模块进行拆分,拆分后,最终通过标签导入到一个主配置文件中,项目加载主配置文件就连同导入的文件一并加载了
java
<!-- 导入用户模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"></import>
<!-- 导入商品模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"></import>
<alias>标签是为某个Bean添加别名,与在标签上使用name属性添加别名的方式一样。
java
// xml文件内容
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl"></bean>
<alias name="userDao" alias="aaa"></alias>
在beanFactory中维护着一个名为aliasMap集合,存储别名和beanName之间的映射关系

9,Spring的get方法

java
// 测试类中内容
// 根据beanName获取容器中的Bean实现,需要手动强转
userService userService = (userService) applicationContext.getBean("userService");
// 根据Bean类型去容器中匹配对应的Bean实例,如存在多个匹配Bean则报错
userDao userDao = applicationContext.getBean(userDao.class);
// 根据beanName获取容器中的Bean实例,指定Bean的Type类型
userService userService1 = applicationContext.getBean("userService", userService.class);
3,Spring配置非自定义Bean
以上在xml配置的Bean都是自己定义的,例如UserDaoImpl。但是在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置。
配置非自定义的Bean需要考虑如下两个问题:
1,被配置的Bean的实例化方式是什么?无参构造,有参构造,静态工厂还是实例工厂方式;
2,被配置的Bean是否需要注入必要属性。
三、Bean实例化的基本流程
Spring容器在进行初始化时,会将xml配置的的信息封装成一个BeanDefinition对象(一个bean对应一个BeanDefinition对象),所有的BeanDefinition存储到一个名为beanDefinitionMap的Map集合中,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为singletonObjects的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象返回。
DefaultListableBeanFactory对象内部维护着一个Map用于存储封装好的BeanDefinitionMap
java
public class DefaultListBeanFactory extends ... implements ...{
// 存储<bean>标签对应的BeanDefinition对象
// key:是Bean的beanName,value:是Bean定义对象BeanDefinition
private final Map<String,BeanDefinition> beanDefinitionMap;
}
Spring框架会取出beanDefinitionMap中的每个BeanDefinition信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对应的Bean的实例化操作。
Bean实例及单例池singletonObjects,beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象,存储到单例池singletonObjects中去,在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中,维护着singletonObjects。
java
// 部分源码
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
总结(Bean实例化的基本流程)
1,加载xml配置文件,解析获取配置中的每个的信息,封装成一个个的BeanDefinition对象;
2,将BeanDefinition存储在一个名为beanDefinitionMap的Map中;
3,ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
4,创建好的Bean实例对象,被存储在一个名为singletonObjects的Map中;
5,当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。

四、SpringBean的生命周期
Spring Bean的生命周期是从Bean实例化之后,即通过反射创建出对象之后,到Bean成为一个完整的对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期,Spring Bean的生命周期大体上分为三个阶段:
1,Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;
2,Bean的初始化阶段:Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。
3,Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。
1,Spring Bean的初始化阶段
Spring Bean的初始化过程涉及如下几个过程:
1,Bean实例的属性填充
2,Aware接口属性注入
3,BeanPostProcessor的before()方法回调
4,InitialzingBean接口的初始化方法回调
5,自定义初始化方法init回调
6,BeanPostProcessor的after()方法回调
1)Bean实例的属性填充
BeanDefinition中有对当前Bean实体的注入信息通过属性propertyValues进行了存储,例如UserService的属性信息如下(beanFactory->beanDefinitionMap->某个beaDefinition对象->value->properValues):

Spring在进行属性注入时,会分为如下几种情况:
注入普通属性,String,int或存储基本类型的集合时,直接通过set方法的反射设置进去
注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完整整个生命周期)后,在进行注入操作;
注入双向对象引用属性时,涉及了循环引用(循环依赖)问题;
多个实体之间相互依赖并形成闭环的情况就叫做"循环依赖",也叫做"循环引用"

java
// userDaoImpl文件内容
private userDao userDao;
public void setUserDao(userDao userDao) {
this.userDao = userDao;
}
// userServiceImpl文件内容
private userServiceImpl userService;
public void setUserService(userServiceImpl userService) {
this.userService = userService;
}
// xml文件内容
<bean id="userService" class="com.mxw.Service.Impl.userServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.mxw.Dao.Impl.UserDaoImpl">
<property name="userService" ref="userService"></property>
</bean>

Spring提供了三级缓存存储完整Bean实例和半成品Bean实例,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:
java
// 1,最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 2,早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用,称之为"二级缓存"
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 3,单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
UserService和UserDao循环依赖的过程结合上述三级缓存详细描述:
1)UserService实例化对象,但尚未初始化,将UserService存储到三级缓存;
2)UserService属性注入,需要UserDao,从缓存中获取,没有UserDao;
3)UserDao实例化对象,但尚未初始化,将UserDao存储到三级缓存;
4)UserDao属性注入,需要UserService,从三级缓存获取UserService(从一级缓存开始查找),UserService从三级缓存移入二级缓存;
5)UserDao执行其他生命周期过程,最终成为了一个完整Bean,存储到一级缓存,删除二三级缓存;
6)UserService注入UserDao;
7)UserService执行其他生命周期过程,最终成为了一个完整Bean,存储到一级缓存,删除二三级缓存。
2)常用的Aware接口
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。

2,SpringIOC整体流程总结

从解决依赖注入的 "痛点",到支撑微服务架构的 "全能",Spring 的价值从来不止于 "简化代码",更在于它以 IOC 为核心搭建的一套灵活、稳定且持续进化的 "生态体系"。无论是初入行的开发者理解对象管理逻辑,还是深耕多年的架构师优化系统耦合度,IOC 都是探索 Spring 绕不开的关键。当然,Spring 的学习之路并非一蹴而就,从 IOC 容器的原理到 AOP 的实际应用,从 Spring Boot 的快速开发到 Spring Cloud 的分布式治理,每一步深入都能带来新的收获。希望这篇文章能成为你探索 Spring 的起点,接下来不妨亲手搭建一个 Spring 项目,在实践中感受 IOC 如何简化依赖管理 ------ 毕竟,最好的学习方式,永远是 "动手去做"。JUST DO IT。