什么是循环依赖
循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。比如A 依赖于B,B依赖于C,C又依赖于A。
注意: 这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结 条件。
Spring中循环依赖场景有:
- 构造器的循环依赖(构造器注入)
- Field 属性的循环依赖(set注入)
其中,构造器的循环依赖问题无法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决
属性循环依赖时,spring采用的是提前暴露对象的方法。
spring循环依赖处理机制
- 单例 bean 构造器参数循环依赖(无法解决)
- 原因:构造参数注入时,对象还没有创建,无法获取对象引用
- prototype 原型 bean循环依赖(无法解决)
- 原因:多例模式下,Bean创建后就不归IOC容器管理,无法注入其他对象中
- 单例bean通过setXxx或者@Autowired进行循环依赖
Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的引用时,对象的属性是可以延 后设置的,但是构造器必须是在获取引用之前。
Spring通过setXxx或者@Autowired方法解决循环依赖;
假设A依赖B,B依赖A:
- 新建A,将引用放入三级缓存中
- 发现A依赖B:
- 从一级缓存查询,没有
- 从二级缓存查询,没有
- 从三级缓存查询,没有
- 新建B,将B引用放入三级缓存中
a. 发现依赖A
b. 从一级缓存找,没有
c. 从二级缓存找,没有
d. 从三级缓存找到,返回注入
e. 走B的剩余生命周期处理 - B新建完成,存入一级缓存(单例池)中
- A注入B完成
- A剩余生命周期处理完成,存入一级缓存中
如果有三个或者四个循环引用,就一级一级初始化,都是从三级缓存中获取引用注入的;
Spring初始化SingletonBean流程
这里附一张我画的spring初始化Bean的时序图供参考
为什么使用三级缓存,而不是用二级缓存呢?
事实上,如果只是解决循环依赖问题,只有二级缓存就够用了,那为什么spring要使用三级缓存来处理循环依赖问题呢?
这里是因为,spring要确保,预先存储的引用(三级缓存中的),要和一级缓存中的引用一样,如果只是用二级缓存,对于那些,只是注入属性,没有其他额外处理的Spring Bean是没有问题的,它的引用就是一开始构造函数创建后的引用,但是对于那些,最终引用会变化的Spring Bean(比如:AOP生成的代理对象)就有大问题了,会出现,三级缓存中的引用(依赖注入的引用)和最终一级缓存中的引用不一样,导致从三级缓存中注入的属性,会丢失(AOP添加的)功能。
所以,Spring 使用三级缓存解决循环依赖,在从三级缓存注入到属性过程中,会预先(AOP部分处理)生成代理对象,然后注入属性,然后放入二级缓存(其他属性注入时可以直接从二级缓存取用同一个代理对象),最后在对象正常生命周期中会判断此对象是否已经生成过代理对象,如果生成过,则从二级缓存中取出,将前一个对象数据拷贝到代理对象中,再继续spring bean生命周期。