Spring源码学习之bean的初始化以及循环引用
什么是bean
在Spring中,bean是指由Spring IoC容器管理的对象。在使用Spring框架的过程中,我们会将一些Java对象放入Spring容器中,这些对象即成为bean。在Spring容器内部,每个bean以及定义它的bean定义都包含有元数据(meta-data),例如一个bean是单例还是原型,其依赖关系及其他。
bean的生命周期
在 Spring 中,bean 的生命周期包括了几个阶段,如下所示:
- 实例化一个 bean;
- 如果涉及到实现
BeanNameAware
接口,会调用setBeanName()
方法,传入该 Bean 的在配置文件中配置的名字。 - 如果涉及到实现
BeanFactoryAware
接口,会调用setBeanFactory()
方法,传入创建它的 BeanFactory。 - 如果涉及到实现
ApplicationContextAware
接口,会调用setApplicationContext()
方法,传入当前的 ApplicationContext 上下文。 - 如果实现了
BeanPostProcessor
接口,会调用postProcessBeforeInitialization()
方法,该方法返回一个对象的实例。 如果目标 bean 是单例的,则在初始化缓存中保存该 bean。 - 如果涉及到实现
InitializingBean
接口,会调用afterPropertiesSet()
方法。 - 如果实现了自定义的初始化方法,则调用其自身定义的初始化方法。
- 如果实现了
BeanPostProcessor
接口,会调用postProcessAfterInitialization()
方法,该方法返回一个对象的实例。 如果目标 bean 是单例的,则将更新后的 bean 放回初始化缓存中。 - 此 bean 可以被使用了。
- 如果该 bean 实现了
DisposableBean
接口,会在容器关闭的时候调用destory()
方法。 - 如果该 bean 实现了自定义的销毁方法,则会调用其自身定义的销毁方法。
bean的初始化
bean的初始化流程可以简单理解为对象的创建和对象内部的一些状态及属性的注入。在Spring容器的初始化过程中,容器会通过反射机制直接调用Java类的构造方法来实例化bean对象。
而对于bean的属性注入,Spring提供了两种方式:
- 基于setter方法的自动注入:Spring遍历所有bean的setter方法,然后通过反射将特定的bean注入到setter方法中。这样的注入方式需要先创建bean对象,再通过调用setter方法来给属性赋值。setter方法是用来设置bean对象中属性值的方法,具体的实现定义在Java代码中。
- 基于构造方法的自动注入:Spring容器使用反射机制直接调用Java类的构造方法来自动注入bean的属性。这种方式会先实例化对象,然后通过调用Java类的构造方法设置参数,最后将设置好的对象注入到Spring容器中。
对于循环依赖的情况,Spring会采用“三级缓存”的方式来解决问题:
- Spring容器在创建bean的时候,首先会在单例缓存(singletonObjects)中查找是否存在给定bean的实例,若有,直接返回实例。若没有,进入第二步。
- 在创建实例之前,Spring会将正在创建的bean放入正在创建缓存(earlySingletonObjects)中。之后Spring会创建bean实例并放入其中,若bean的属性需要注入其他bean,则会调用getBean()方法来调用相应的bean,容器会在此过程中递归创建bean。当递归创建bean时,若从单例缓存中检测到已有其实例,将其中的proxy换成singletonObjects中的实例。若此时earlySingletonObjects缓存中出现两个及以上的实例,说明此时发生了循环依赖,则进入第三步。
- 此时,若当前bean正在尝试给其他bean注入并且正在获取其他bean的实例时,Spring容器会创建一个代理对象回去。当被依赖的bean创建完成后,再将代理对象替换成真正的实例。
示例说明
示例一
我们来看一个简单的例子来说明bean的初始化以及循环引用问题,如下所示:
public class BeanA {
private BeanB beanB;
public BeanB getBeanB() {
return beanB;
}
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
public void init() {
System.out.println("BeanA init");
}
}
public class BeanB {
private BeanA beanA;
public BeanA getBeanA() {
return beanA;
}
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
public void init() {
System.out.println("BeanB init");
}
}
在这个例子中,BeanA
和BeanB
互相依赖,BeanA
依赖于BeanB
,BeanB
又依赖于BeanA
。为了让这个例子更加生动,我们在BeanA
和BeanB
的init
方法中给出一些输出。
针对这个例子,我们需要在Spring容器的配置文件中定义两个bean,否则Spring不知道如何创建这两个类的实例:
<bean id="beanA" class="com.xxx.BeanA">
<property name="beanB" ref="beanB"/>
</bean>
<bean id="beanB" class="com.xxx.BeanB">
<property name="beanA" ref="beanA"/>
</bean>
如果我们运行代码,我们会发现会报BeanCurrentlyInCreationException
的错误,这说明在Spring的创建bean的过程中出现了循环依赖,我们需要采取措施来解决这个问题。
为了解决这个问题,我们可以改变BeanA
和BeanB
的初始化方式:改为使用构造函数进行初始化。
public class BeanA {
private BeanB beanB;
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
public void init() {
System.out.println("BeanA init");
}
}
public class BeanB {
private BeanA beanA;
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
public void init() {
System.out.println("BeanB init");
}
}
当采用构造函数进行初始化时,我们需要在Spring配置文件中加入以下内容:
<bean id="beanA" class="com.xxx.BeanA">
<constructor-arg ref="beanB" />
</bean>
<bean id="beanB" class="com.xxx.BeanB">
<constructor-arg ref="beanA" />
</bean>
现在我们可以运行该程序来实现bean的实例化和初始化了,而且也不再出现循环引用问题。
示例二
我们来看一个更加实际的例子,在实际开发中经常会遇到的问题,通过该示例,我们将进一步说明bean的初始化以及循环引用。
我们假设有两个类:DataSource
和TransactionManager
。其中,TransactionManager
类依赖于DataSource
类。
public class DataSource {
private String url;
private String username;
private String password;
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void init() {
System.out.println("DataSource init");
}
}
public class TransactionManager {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void init() {
System.out.println("TransactionManager init");
}
}
在Spring容器的配置文件中定义两个bean:
<bean id="dataSource" class="com.xxx.DataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<bean id="transactionManager" class="com.xxx.TransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
如果我们运行该程序,我们会在控制台上看到以下输出:
DataSource init
TransactionManager init
以上输出表明,在Spring容器初始化的时候,会先初始化DataSource
对象,然后再初始化TransactionManager
对象。
总结
本文通过对Spring源码的学习,通过编写实际的代码来说明了bean的初始化及循环引用问题,对于初学者而言,需要理解Spring的bean的生命周期以及基础知识,并清楚地了解Spring容器在其创建过程中处理循环依赖的机制。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:spring源码学习之bean的初始化以及循环引用 - Python技术站