【Spring源码分析】循环依赖的处理
[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/Spring/Spring-Circular-Dependencies.html
看源码的同学可以查看我的GitHub 上面在官方的基础上加入了
大量中文注释
,帮助理解
要了解的知识
什么是循环依赖
graph LR A-->B B-->C C-->A
存在哪些循环依赖
- Setter循环依赖(可以被解决)
- 构造循环依赖(报错)
- 基于Prototype类型的循环依赖(报错)
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 只会放入
[创建完成(只调用了构造方法)]
的beanFactory (用于解决[循环依赖]
的3级缓存)
解决循环依赖核心代码
1 |
|
其实这里做的事情和上面原理说的一样:
因为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 | A a = new A(); |
其它一些补充
为什么构造循环依赖不能被解决(Bean创建过程中没有放入缓存)
根据上图的流程,createBeanInstance调用的实际上是
构造方法
,调用前还没把任何东西放入缓存
中。
这时候如果出现基于构造方法的循环依赖
,那么是不可能成功的,试想一下:
newA的时候依赖于B
newB的时候依赖于C
newC的时候又反过来依赖于刚才的A
而这时候A还没被new出来!这时候异常就出现了
上述情况换成代码理解就成了
1 | A a = new A(new B(new C(new A(new ..........)))); |
为什么prototype类型的循环依赖无法被解决
首先咱们得先了解下prototype的bean创建流程
graph TD A[createBeanInstance] C[populateBean] E[return Bean] A--调用Bean的构造方法得到一个空Bean-->C C--为Bean注入属性-->E
从流程可见,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(......)))); |
总结
至此,循环依赖的讨论到此结束。其实,说白了就是:
- 只有
[单例Bean之间]
的的循环依赖才能被解决(因为只有单例Bean会被缓存到2级/3级缓存中 用以解决循环依赖) - 只有
[基于Setter属性注入]
循环依赖才能被解决(因为只有基于Setter属性注入的Bean,才能在通过new调用构造方法后,把bean放入2级/3级缓存) [构造注入]
和[prototype类型的bean]
的循环依赖无法被解决 (因为没办法放入2级/3级缓存中)
【Spring源码分析】循环依赖的处理
https://heyfl.gitee.io/Spring/Spring-Circular-Dependencies.html