Spring 是如何解决循环依赖的?

循环依赖解决方式: 三级缓存

第一个demo标红的错误堆栈部分信息清晰的看出bean创建基本流程, 由refresh()为入口切入, 这里只分析单例bean创建流程: 

1)、AbstractBeanFactory.getBean为入口 并委托 AbstractBeanFactory.doGetBean创建
2)、AbstractBeanFactory.doGetBean 会首先从AbstractBeanFactory.getSingleton中获取缓存的bean对象, 如果不存在则调用抽象方法createBean, 即子类实现的AbstractAutowireCapableBeanFactory.createBean方法
3)、AbstractAutowireCapableBeanFactory.createBean方法触发doCreateBean依次调用以下方法实现bean创建过程

  • createBeanInstance: 实例化bean, 如果需要依赖其他对象则首先创建其他对象(发生循环依赖的地方)
  • addSingletonFactory: 将实例化bean加入三级缓存
  • populateBean: 初始化bean, 如果需要依赖其他对象则首先创建其他对象(发生循环依赖的地方)
  • initializeBean
  • registerDisposableBeanIfNecessary

4)、AbstractAutowireCapableBeanFactory.autowireConstructor使用构造函数进行实例化

5)、最终调用 ConstructorResolver.autowireConstructor 和 ConstructorResolver.resolveConstructorArguments 进行实例化已经解析构造参数

6)、调用BeanDefinitionValueResolver.resolveValueIfNecessary 和 BeanDefinitionValueResolver.resolveReference 模版类解析构造参数

这里只分析流程主干代码:

 AbstractBeanFactory为bean创建的入口

 AbstractBeanFactory.java

在DefaultSingletonBeanRegistry使用三级缓存:

// 第一层: 初始化完备的单例bean
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 第二层: 提前暴光的单例对象的Cache 
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 第三层: ObjectFactory工厂bean缓存, 存储实例话后的bean Factory
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

所谓Spring的循环依赖,指的是这样一种场景:

当我们注入一个对象A时,需要注入对象A中标记了某些注解的属性,这些属性也就是对象A的依赖,把对象A中的依赖都初始化完成,对象A才算是创建成功。那么,如果对象A中有个属性是对象B,而且对象B中有个属性是对象A,那么对象A和对象B就算是循环依赖,如果不加处理,就会出现:创建对象A-->处理A的依赖B-->创建对象B-->处理B的对象A-->创建对象A-->处理A的依赖B-->创建对象B......这样无限的循环下去。

这事显然不靠谱。

Spring处理循环依赖的基本思路是这样的:

虽说要初始化一个Bean,必须要注入Bean里的依赖,才算初始化成功,但并不要求此时依赖的依赖也都注入成功,只要依赖对象的构造方法执行完了,这个依赖对象就算存在了,注入就算成功了,至于依赖的依赖,以后再初始化也来得及(参考Java的内存模型)。

因此,我们初始化一个Bean时,先调用Bean的构造方法,这个对象就在内存中存在了(对象里面的依赖还没有被注入),然后把这个对象保存下来,当循环依赖产生时,直接拿到之前保存的对象,于是循环依赖就被终止了,依赖注入也就顺利完成了。

举个例子:

假设对象A中有属性是对象B,对象B中也有属性是对象A,即A和B循环依赖。
  1. 创建对象A,调用A的构造,并把A保存下来。
  2. 然后准备注入对象A中的依赖,发现对象A依赖对象B,那么开始创建对象B。
  3. 调用B的构造,并把B保存下来。
  4. 然后准备注入B的构造,发现B依赖对象A,对象A之前已经创建了,直接获取A并把A注入B(注意此时的对象A还没有完全注入成功,对象A中的对象B还没有注入),于是B创建成功。
  5. 把创建成功的B注入A,于是A也创建成功了。

于是循环依赖就被解决了。

下面从Spring源码的角度看一下,具体是个什么逻辑。

在注入一个对象的过程中,调用了这样一个方法:

Object sharedInstance = this.getSingleton(beanName);

这段代码在AbstractBeanFactory类的doGetBean()方法中。

这里得到的Object就是试图是要创建的对象,beanName就是要创建的对象的类名,这里getSingleton()方法的代码如下:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

这个方法是Spring解决循环依赖的关键方法,在这个方法中,使用了三层列表来查询的方式,这三层列表分别是:

singletonObjects
earlySingletonObjects
singletonFactories

这个方法中用到的几个判断逻辑,体现了Spring解决循环依赖的思路,不过实际上对象被放入这三层的顺序是和方法查询的循序相反的,也就是说,在循环依赖出现时,对象往往会先进入singletonFactories,然后earlySingletonObjects,然后singletonObjects。

下面看一下这个方法的代码逻辑:

1,

Object singletonObject = this.singletonObjects.get(beanName);

方法首先从singletonObjects中获取对象,当Spring准备新建一个对象时,singletonObjects列表中是没有这个对象的,然后进入下一步。

2,

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))

除了判断null之外,有一个isSingletonCurrentlyInCreation的判断,实际上当Spring初始化了一个依赖注入的对象,但还没注入对象属性的时候,Spring会把这个bean加入singletonsCurrentlyInCreation这个set中,也就是把这个对象标记为正在创建的状态,这样,如果Spring发现要创建的bean在singletonObjects中没有,但在singletonsCurrentlyInCreation中有,基本上就可以认定为循环依赖了(在创建bean的过程中发现又要创建这个bean,说明bean的某个依赖又依赖了这个bean,即循环依赖)。

举个例子:对象A和对象B循环依赖,那么初始化对象A之后(执行了构造方法),要把A放入singletonsCurrentlyInCreation,对象A依赖了对象B,那么就要再初始化对象B,如果这个对象B又依赖了对象A,也就是形成了循环依赖,那么当我们注入对象B中的属性A时,进入这个代码逻辑,就会发现,我们要注入的对象A已经在singletonsCurrentlyInCreation中了,后面的逻辑就该处理这种循环依赖了。

3,

singletonObject = this.earlySingletonObjects.get(beanName);

这里引入了earlySingletonObjects列表,这是个为了循环依赖而存在的列表,从名字就可以看到,是个预创建的对象列表,刚刚创建的对象在这个列表里一般也没有。

4,

if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);

earlySingletonObjects也没有则从singletonFactories中获取,前面说到singletonFactories是对象保存的第一步,实际上对象初始化后,可能还没有注入对象的依赖,就把对象放入了这个列表。

如果是循环依赖,此时的singletonFactories中一般是会存在目标对象的,举个例子:对象A和对象B循环依赖,那么初始化了对象A(执行了构造方法),还没有注入对象A的依赖时,就会把A放入singletonFactories,然后开始注入A的依赖,发现A依赖B,那么需要构对象B,构造过程也是执行了B的构造后就把B放到singletonFactories,然后开始注入B的依赖,发现B依赖A,在第二步中提到,此时A已经在singletonsCurrentlyInCreation列表里了,所以会进入此段代码逻辑,而且此时时对象A在singletonFactories中确实存在,因为这已经是第二次试图创建对象A了。

5,

if (singletonFactory != null) {
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
}

代码到这里基本已经确定我们要创建的这个对象已经发生循环依赖了,然后Spring进行了这样的操作,把这个对象加入到earlySingletonObjects中,然后把该对象从singletonFactories中删掉。

6,其实上面5步已经执行完了该方法的代码,这里加的第6步是为了解释循环依赖的结果。在这个方法的代码之后,会把bean完整的进行初始化和依赖的注入,在完成了bean的初始化后,后面代码逻辑中会调用一个这样的方法:

getSingleton(String beanName, ObjectFactory<?> singletonFactory)

这个方法中有个小小的子方法addSingleton(),他的代码是这样的:

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

这个方法处理的是已经注入完依赖的bean,把bean放入singletonObjects中,并把bean从earlySingletonObjects和singletonFactories中删除,这个方法和上面分析的方法组成了Spring处理循环依赖的逻辑。

综上,Spring处理循环依赖的流程大概就是以下这样,假设对象A和对象B循环依赖:

以上,希望我说清楚了。

另外,SpingBoot启动流程的源码分析

https://blog.csdn.net/lkforce/article/details/95456154

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swordbob

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值