循环依赖问题
什么是循环依赖
首先看一下下面的Spring配置文件
<!-- beanA依赖于beanB -->
<bean id="beanA" class="top.okay3r.ClassA">
<property name="beanB" ref="beanB"/>
</bean>
<!-- beanB依赖于beanA -->
<bean id="beanB" class="top.okay3r.ClassB">
<property name="beanA" ref="beanA"/>
</bean>
当IOC容器读取上面的配置时,就会先对beanA进行加载;在对beanA进行属性填充时,会发现beanA依赖于beanB,然后就会对beanB进行加载;当对beanB进行属性填充时,又会发现beanB依赖于beanA,于是就加载beanA...
可以想到,如果Spring的容器对于这种循环依赖问题不作出响应的处理,那么就会无限执行上面的过程。最终的结果就可能造成OOM从而导致程序崩溃
WX20200213-173232@2x.png
Spring中bean注入的方式
我们知道在Spring中,注入bean的方式有【构造器注入】和【setter注入】两种方式。但在我们使用Spring管理bean时,可能会遇到一种特殊的情况,那么就是上面所说的循环依赖问题
我们再看一下Spring创建bean的过程
Spring创建bean的过程
如果阅读过IOC相关的源码就会知道,创建bean的过程大体可以分为初始化bean
,对bean的属性进行填充
,对bean进行初始化
三个步骤
- 初始化bean:即new一个bean实例,是通过反射调用构造器实现的
- 对bean的属性进行填充:可以理解为对<property>标签相应的属性进行赋值
- 对bean进行初始化:即调用事先配置好的
init-method
方法,所以可以将一些初始化的行为写到这个方法中
然后就来分析一下两种注入方式
构造器注入
在普通的java程序中,如果已经new出了一个对象,我们就知道这个对象已经是可用的了,不论它的属性是否完整。
但在Spring中,创建出来的bean必须要完成三个步骤才能被认为是可用的,才会将这个“完整”的bean放入到IOC容器中。
因为构造器注入是在实例化对象时反射调用构造器去注入参数,所以既然beanA、beanB的都拿不到完整的依赖,就会进行无限的循环调用,从而无法解决【循环依赖问题】。解决办法就只有是修改依赖关系了
setter注入
再看一下setter注入方式
setter注入方式就是new出一个对象后,调用该对象的set方法对属性进行赋值。此时对象已经被new出来了,只不过是不完整而已。
如果出现了循环依赖的问题,这就要比构造器注入的方式好的多
所以Spring对于循环依赖问题的解决就是针对于setter方法的
接下来就开始分析Spring是如何解决循环依赖问题的
Spring对于循环依赖的解决
先提前知道一下问题大概是怎样解决的
首先我们要知道,Spring对于循环依赖的问题是采用【缓存】的方式解决的
看一下Spring源码中的DefaultSingletonBeanRegistry类(注:SingletonBeanRegistry接口提供了关于访问单例bean的功能,DefaultSingletonBeanRegistry就是该接口的默认实现)
/** Cache of singleton objects: bean name to bean instance. */
// 用于存储完整的bean,接下来称之为【一级缓存】
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
// 用于存储不完整的bean,即只是new出来,并没有属性值的bean,接下来称之为【二级缓存】
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
//用于存储bean工厂对象,接下来称之为【三级缓存】
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
大概捋一遍bean的获取、创建过程
因为循环依赖都是产生在获取bean时,所以我们直接从AbstractBeanFactory的getBean()方法开始
- AbstractBeanFactory#getBean()没什么自身的实现,只调用了doGetBean()
- AbstractBeanFactory#doGetBean(),在这个方法中调用了getSingleton(beanName)获取实例:
Object sharedInstance = getSingleton(beanName);
- 判断sharedInstance是否为null,如果不为null则调用getObjectForBeanInstance处理,然后返回。也就是IOC容器获取bean成功,可以拿去使用了。如果sharedInstance为null,则调用getSingleton(beanName,Object{...})方法
- DefaultSingletonBeanRegistry#getSingleton中,首先会从【一级缓存】中get一下bean,如果获取不到,则会进入创建bean的流程
- 创建bean的主要逻辑就是走AbstractAutowireCapableBeanFactory#doCreateBean,先是使用createBeanInstance方法创建bean的实例,然后对bean进行初始化,再进行属性填充....然后返回bean
- 获取到bean,完成
上面并没有涉及到循环依赖和二级、三级缓存的问题,因为对于循环依赖的处理,都表现在代码中的细节之处
对应上面的过程,从源码上开始分析
首先看doGetBean方法
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException{
// 从缓存中获取单例bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) { //如果获取到单例bean,则走下面代码
//......
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}else {//如果没有获取到单例bean,则走下面代码
//......
// 如果是单例的Bean,请下面的代码
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
// 创建单例Bean的主要方法,返回的bean是完整的
return createBean(beanName, mbd, args);
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
//......
}
return (T) bean;
}
}
上面的代码中,sharedInstance是通过getSingleton()方法获得的,实际上getSingleton(beanName)方法没什么逻辑,内部调用了getSingleton(beanName, boolean)这个方法,所以接下来就进入到这个方法中
getSingleton(beanName, boolean)的实现
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存中获取单例对象
Object singletonObject = this.singletonObjects.get(beanName);
// isSingletonCurrentlyInCreation : 判断当前单例bean是否正在创建中,也就是没有初始化完成。比如beanA的构造器依赖了beanB对象所以得先去创建B对象,或者在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的beanA就是处于创建中的状态
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从二级缓存中获取单例bean
singletonObject = this.earlySingletonObjects.get(beanName);
// allowEarlyReference :是否允许从singletonFactories中通过getObject拿到对象
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存中获取单例bean
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过单例工厂获取单例bean
singletonObject = singletonFactory.getObject();
// 从三级缓存移动到了二级缓存,并移除singletonFactory
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
从上面的代码中可以总结出以下几点:
- 先从【一级缓存】中查找,有则直接返回
- 如果在【一级缓存】中获取不到,并且对象正在创建中(beanName包含在singletonsCurrentlyInCreation),那么就再从【二级缓存】中查找,有则直接返回
- 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取,就从【三级缓存】中获取(
singletonFactory.getObject()
)。通过ObjectFactory获取到的对象,是进行代理后的对象(假设有AOP)。将从【三级缓存】中获取到的对象放到【二级缓存】中,同时删除此beanName对应的【三级缓存数据】
再看一下doGetBean()方法中刚刚没有讲到的“if-else”部分
如果getSingleton()方法获取到了bean,即sharedInstance不为null,则对其进行处理然后返回
如果sharedInstance为null,就要走else中的代码了
首先判断一下是否为单例,(mbd是通过读取配置文件中bean标签生成的bean的定义信息,具体获得的方法这里不详细说了)。因为多例的bean是不需要放入到IOC容器中的,所以这里只处理单例bean
如果为单例,则调用getSingleton(String beanName, ObjectFactory<?> singletonFactory)
方法
getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// ......
// 创建 bean 实例
singletonObject = singletonFactory.getObject();
newSingleton = true;
if (newSingleton) {
// 添加新创建的bean添加到【一级缓存】中,并删除其他缓存中对应的bean
addSingleton(beanName, singletonObject);
}
// ......
// 返回 singletonObject
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 将新创建的bean添加到【一级缓存】中
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
// 从其他缓存中移除相关的bean
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
上面的代码主要包含了两个功能
- 获取完整的bean实例
- 将新的bean添加到【一级缓存】中,以后getBean的时候就可以直接获取了
可以看到bean实例是由singletonFactory.getObject()
拿到的,也就是通过doGetBean()
方法中判断是否单例后的匿名内部类获取到的,从而知道获取到的bean是由createBean()方法创建的
creatBean()方法调用了doCreatBean()方法,所以实际的创建逻辑就再doCreatBean()中
doCreatBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 默认调用无参构造实例化Bean
// 构造方法的依赖注入,就是发生在这一步
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 实例化后的Bean对象,这里获取到的是一个原始对象,即没有进行属性填充的对象
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
//......
// 解决循环依赖的关键步骤
// earlySingletonExposure:是否”提前暴露“原始对象的引用
// 因为不论这个bean是否完整,他前后的引用都是一样的,所以提前暴露的引用到后来也指向完整的bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 如果需要提前暴露单例bean,则将该bean工厂放入【三级缓存】中
if (earlySingletonExposure) {
// 将刚创建的bean工厂放入三级缓存中singleFactories(key是beanName,value是FactoryBean)
// 同样也会移除【二级缓存】中对应的bean,即便没有
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//填充属性(依赖注入)
populateBean(beanName, mbd, instanceWrapper);
//调用初始化方法,完成bean的初始化操作
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//......
return exposedObject;
}
ok,看到这里,整个在有循环依赖问题下创建、获取bean的流程就结束了
举个例子,从头串一下流程。假设beanA->beanB, beanB->beanA,即A、B相互依赖
- 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
- 在getSingleton()方法中,从一级缓存中查找,没有,返回null
- doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
(ps:现在是2020.2.14 凌晨1点07分,情人节了,因为疫情不能和小杨一起,在我的第一篇博客中纪念一下这个节日😂,祝所有人情人节快乐) - 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法
- 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断:是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否在第四步的集合中)。判断为true,则将beanA添加到【三级缓存】中
- 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
- 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
- 此时beanB依赖于beanA,调用getSingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
- 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
- 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getSingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中
最后
整个过程大概就是这样了,由于spring的源码比较多,就只挑选了重点部分进行注释
其实主要思想就是利用二级、三级缓存对未初始化完成的bean进行提前的引用暴露,也就是将其设置为可引用的,这样当依赖于他的bean在进行属性填充时就可以直接拿到引用,解决了死循环的问题
============================================================
还有几个比较重要的点,在这里指出位置,可以根据这些去查找看
- 这三个级别的缓存,在同一时间,同一beanName对应的bean只会存在于一个缓存中
- 如果没有循环依赖的问题,二级、三级缓存是没有用处的,体现在AbstractAutowireCapableBeanFactory#doCreateBean的判断earlySingletonExposure这个地方
- 判断循环依赖,是用一个Set集合实现的,正在创建中的beanName会加到这个集合中
- 三级缓存其实还有创建AOP代理的功能,在AbstractAutowireCapableBeanFactory#createBean调用resolveBeforeInstantiation的位置。而如果没有循环依赖问题,那么代理就是在调用init-method过程中创建的
- bean实例化之后,属性填充之前,如果有循环依赖,就将这个bean封装到一个ObjectFactory然后放到三级缓存中(为了提前暴露引用)
- 三级缓存中的ObjectFactory第一次拿出被他保存bean后,这个bean就会进入二级缓存
- bean被创建完整后,进入一级缓存
》》》》》》》》》》》》》》》》》》》》》
有些东西不知道怎么转述成语言表达出来,还有如果有不好的或者说错的地方希望看过的大佬能帮忙指正,谢谢~~
网友评论