【Spring源码分析】循环依赖的处理

[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.github.io/Spring/Spring-Circular-Dependencies.html

看源码的同学可以查看我的GitHub 上面在官方的基础上加入了大量中文注释,帮助理解,喜欢的话也可以给个Star XD


要了解的知识

什么是循环依赖

【Spring源码分析】循环依赖的处理_第1张图片

存在哪些循环依赖

  1. Setter循环依赖(可以被解决)
  2. 构造循环依赖(报错)
  3. 基于Prototype类型的循环依赖(报错)

Bean的创建步骤

【Spring源码分析】循环依赖的处理_第2张图片uploading.4e448015.gif转存失败重新上传取消【Spring源码分析】循环依赖的处理_第3张图片uploading.4e448015.gif转存失败重新上传取消【Spring源码分析】循环依赖的处理_第4张图片uploading.4e448015.gif转存失败重新上传取消【Spring源码分析】循环依赖的处理_第5张图片

bean的创建流程

看源码的同学可以找到源码:
环节1~4的代码在AbstractAutowireCapableBeanFactory#doCreateBean方法中

环节5的代码在DefaultSingletonBeanRegistry#getSingleton(String,ObjectFactory)方法的addSingleton(beanName, singletonObject);


Spring是怎么处理循环依赖的(对于单例Bean)

实现原理

Spring在创建单例BeanA的时候会先把BeanA(仅执行完构造方法)给放到三级缓存中,
当其他Bean或业务代码在BeanA[创建完之前]需要用到,
那么Spring就会把这个 还没进行[属性注入] [调用init方法]的BeanA提前暴露给这些Bean,并且把BeanA提到二级缓存中

三级缓存

首先咱们得知道三级缓存包括哪些:

  • DefaultSingletonBeanRegistry#singletonObjects

单例对象的cache 只有[创建完成(只调用了构造方法)]&&[初始化属性完成]的才会放入这里 (这是一级缓存 我们平时说Spring是个大工厂,所有的创建好的bean都可以从Spring缓存里拿。 这里面说的缓存就是一级缓存)

  • DefaultSingletonBeanRegistry#earlySingletonObjects

存放提前曝光的单例对象 只会放入[创建完成(只调用了构造方法)]&& [被提前曝光的] 的bean (用于解决[循环依赖]的2级缓存)

  • DefaultSingletonBeanRegistry#singletonFactories

单例对象工厂的cache 只会放入[创建完成(只调用了构造方法)]的bean (用于解决[循环依赖]的3级缓存)

解决循环依赖核心代码

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //desc  先从singletonObjects(一级缓存)取
    Object singletonObject = this.singletonObjects.get(beanName);

    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        //desc  取不到且创建中,那么这里使用同步锁阻塞一会,等待创建完成
        //❤这里会发现 当真正创建完bean时会调用addSingletonFactory()  这时候也会锁住singletonObjects❤
        synchronized (this.singletonObjects) {
            //desc 同步阻塞+尝试从earlySingletonObjects(二级缓存)获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                //desc 如果二级缓存取不到&&允许从singletonFactories通过getObject获取
                //desc 通过singletonFactory.getObject()(三级缓存)获取工厂创建该bean
                ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //desc 通过三级缓存的Factory创建目标bean  并放入2级缓存
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}
   

其实这里做的事情和上面原理说的一样:
因为Spring在创建单例BeanA的时候会先把仅仅初始化完成,未注入属性的BeanA(对应图中步骤1)给放到三级缓存中,
如果其他Bean在BeanA创建完之前需要用到(循环依赖就是这种场景),
那么Spring就会把这个BeanA提前暴露给这些Bean,并且把BeanA提到二级缓存中。

这样子,这些Bean就成功的获取到一个仅仅初始化完成,未注入属性的BeanA

场景分析

假设现在有3个Bean ABC发生了Setter循环依赖(如本文最上面的图)
通过如上方式,,Spring完全可以帮我们解决单例之间的Setter循环依赖问题让循环依赖的Bean之间获取到一个仅仅初始化完成,未注入属性的BeanA
这么说可能有些抽象,咱们尝试来还原该情况发生时发生了什么事情

  • 现在getBeanA,会先去缓存里getA,这时候A还没被创建,故进行A的创建流程
  • 因为A依赖于B,所以A会执行到createBean流程第三步的populateBean,然后会去getB(经过了第二步A已经被放入三级缓存中)
  • 同理,B依赖C,所以B会执行到createBean流程第三步的populateBean,然后会去getC(经过了第二步B已经被放入三级缓存中)
  • C依赖于A,也同理,但是这时候再去getA的时候,会发现又是一个getBeanA流程,但是这时候在三级缓存里getA已经能get到对象了
  • 获取到这个缓存中的A之后,完成beanC的属性注入、初始化等操作。这样,就完成beanC的整个创建流程;
  • 同理,B、A也一样: 这样逐级返回,这样,就完成了整个getBeanA的流程,虽然过程中存在循环依赖,但不会令getBean流程出现异常

至此,Spring完美地给我们处理了Setter循环依赖。

其实,换成代码理解可能更加简单:D

1
2
3
4
5
6
7
A a = new A();
B b = new B();
C c = new C();

a.setB(b);
b.setC(c);
c.setA(a);

其它一些补充

为什么构造循环依赖不能被解决(Bean创建过程中没有放入缓存)

参考createBean流程

根据上图的流程,createBeanInstance调用的实际上是构造方法,调用前还没把任何东西放入缓存中。
这时候如果出现基于构造方法的循环依赖,那么是不可能成功的,试想一下:
newA的时候依赖于B
newB的时候依赖于C
newC的时候又反过来依赖于刚才的A
而这时候A还没被new出来!这时候异常就出现了

上述情况换成代码理解就成了

1
A a = new A(new B(new C(new A(new ..........))));

为什么prototype类型的循环依赖无法被解决

首先咱们得先了解下prototype的bean创建流程

调用Bean的构造方法得到一个空Bean

为Bean注入属性

createBeanInstance

populateBean

return Bean

从流程可见,prototype的Bean创建过程中压根就没有对生成完的bean进行缓存
每一个Bean都是实时创建/使用
根据上面的流程,我们很容易发现,这种不使用缓存的情况压根不允许存在循环依赖,因为:

  • A依赖于B,这得实时去创建A和B
  • B依赖于C,也得实时去创建C
  • 创建C的时候发现C依赖于A,然鹅A没被缓存(当然,其实prototype压根不会去查缓存)
  • 那这时候程序就会无限循环下去了(当然 spring会检测到这种异常,中断这次getBean)

所以,prototype类型的循环依赖是不能被解决、也不允许出现的

伪代码大致可以理解为:

1
A a = new A(new B().setC(new C().setA(new A().setB(......))));

总结

至此,循环依赖的讨论到此结束。其实,说白了就是:

  1. 只有[单例Bean之间]的的循环依赖才能被解决(因为只有单例Bean会被缓存到2级/3级缓存中 用以解决循环依赖)
  2. 只有[基于Setter属性注入]循环依赖才能被解决(因为只有基于Setter属性注入的Bean,才能在通过new调用构造方法后,把bean放入2级/3级缓存)
  3. [构造注入] 和 [prototype类型的bean]的循环依赖无法被解决 (因为没办法放入2级/3级缓存中)

你可能感兴趣的:(Spring-源码解析)