循环依赖
1. 介绍
在Spring中的循环依赖就是指一个或者多个Bean之间存在着互相依赖的关系,并且形成了循环调用。
例如:在Spring中,Bean-A依赖Bean-B,Bean-B又依赖Bean-A,Bean-A和Bean-B之间就形成了相互依赖的关系。在创建A对象时,发现A对象依赖了B对象,此时先去创建B对象;创建B对象时,发现B对象又依赖了A对象,此时又区创建A对象......形成死循环。
同理,在Spring中多个对象之间有可能存在循环依赖。
2. 循环依赖类型
- 自我依赖:Bean自己依赖自己,从而形成的循环依赖,一般情况下不会发生这种循环依赖。
- 直接依赖:一般时发生在两个对象之间,A依赖B、B依赖A
- 间接依赖:一般是发生在三个或三个以上对象之间互相依赖的场景。A依赖B、B依赖C、C依赖A......
3. 循环依赖场景
- 构造器循环依赖
- 这种情况发生在两个或多个Bean在其构造函数中相互依赖对方。例如,
BeanA
的构造函数需要一个BeanB
的实例,而BeanB
的构造函数又需要一个BeanA
的实例。 - 结果 :Spring无法解决构造器循环依赖,因为这会导致死锁。当Spring尝试创建
BeanA
时,它会因为缺少BeanB
而阻塞;同样,当Spring尝试创建BeanB
时,它会因为缺少BeanA
而阻塞。
- 这种情况发生在两个或多个Bean在其构造函数中相互依赖对方。例如,
- Setter方法注入循环依赖:
- 这种情况发生在两个或多个Bean在其setter方法或通过其他方法注入中相互依赖对方。例如,
BeanA
有一个setter方法需要一个BeanB
的实例,而BeanB
有一个setter方法需要一个BeanA
的实例。 - 结果:Spring可以解决Setter/方法注入循环依赖,因为它允许在Bean的属性被填充后再进行其他Bean的属性注入。
- 这种情况发生在两个或多个Bean在其setter方法或通过其他方法注入中相互依赖对方。例如,
4. 解决方案
4.1 解决Setter方法注入循环依赖
-
三级缓存机制:
-
一级缓存(singletonObjects):存储已经完全初始化好的Bean实例。
-
二级缓存(earlySingletonObjects):存储Bean的早期引用,这些Bean实例已经实例化但还未完成属性注入和初始化。
-
三级缓存(singletonFactories):存储Bean工厂对象,用于创建二级缓存中的Bean实例。
-
-
解决过程:
-
当Spring容器开始创建一个Bean时,它会首先检查三级缓存中是否已经有这个Bean的工厂对象。如果有,说明这个Bean正在创建中,Spring容器会从三级缓存中获取工厂对象,创建Bean的早期引用,并将其放入二级缓存中。
-
然后,Spring容器会继续处理这个Bean的属性注入。如果属性依赖其他Bean,Spring容器会尝试从二级缓存中获取这些Bean的早期引用,而不是等待它们完全初始化。
-
当所有属性注入完成后,Spring容器会将Bean从二级缓存移动到一级缓存,并执行Bean的初始化方法。
-
如果在属性注入过程中发现循环依赖,Spring容器可以利用二级缓存中的早期引用来解决这个问题,因为这些早期引用已经包含了部分初始化的数据
-
4.2 解决构造器循环依赖
对于构造器循环依赖,Spring没有内置的解决方案。解决这类问题的常见方法包括:
- 重新设计组件:重新设计组件之间的依赖关系,避免构造器循环依赖。
- 使用Setter注入:将构造器注入改为Setter注入,利用Spring的循环依赖解决方案。
- 使用原型Bean:将其中一个Bean的scope设置为prototype,因为原型Bean在每次请求时都会创建新的实例,从而避免循环依赖。