Spring 框架中的循环依赖是指两个或多个 Bean 之间相互依赖,导致它们在初始化时相互引用,从而形成一个闭环。例如,Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。如果不加以处理,循环依赖可能会导致 Spring 容器无法正常初始化。
Spring 通过一系列机制来处理循环依赖,以下是其处理原理的详细解析:
1. Spring 中的循环依赖类型
Spring 中的循环依赖分为三种类型:
(1)构造器循环依赖
构造器循环依赖是指两个或多个 Bean 通过构造器相互依赖。例如:
@Component
public class BeanA {
private BeanB beanB;
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
@Component
public class BeanB {
private BeanA beanA;
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
}
处理方式: Spring 不支持构造器循环依赖。因为构造器注入需要在 Bean 完全初始化之前完成,而循环依赖会导致 Spring 无法确定初始化顺序,从而抛出异常。
(2)单例 Bean 的 setter 循环依赖
单例 Bean 的 setter 循环依赖是指两个或多个单例 Bean 通过 setter 方法相互依赖。例如:
@Component
public class BeanA {
private BeanB beanB;
@Autowired
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
@Component
public class BeanB {
private BeanA beanA;
@Autowired
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
处理方式: Spring 支持单例 Bean 的 setter 循环依赖。通过三级缓存机制来解决。
(3)原型 Bean 的循环依赖
原型 Bean 的循环依赖是指两个或多个原型 Bean 通过 setter 方法相互依赖。例如:
@Component
@Scope("prototype")
public class BeanA {
private BeanB beanB;
@Autowired
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
@Component
@Scope("prototype")
public class BeanB {
private BeanA beanA;
@Autowired
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
处理方式: Spring 不支持原型 Bean 的循环依赖。因为原型 Bean 的生命周期由调用者管理,每次请求都会创建新的实例,Spring 无法保证循环依赖的正确性。
2. Spring 处理单例 Bean 循环依赖的三级缓存机制
Spring 使用三级缓存来解决单例 Bean 的 setter 循环依赖问题。三级缓存分别是:
(1)一级缓存:singletonObjects
一级缓存是一个 ConcurrentHashMap
,用于存储完全初始化完成的单例 Bean。Bean 的初始化过程包括实例化、依赖注入和初始化方法调用。只有当 Bean 完全初始化完成后,才会被放入一级缓存。
(2)二级缓存:earlySingletonObjects
二级缓存是一个 ConcurrentHashMap
,用于存储提前曝光的单例 Bean。当 Bean 还在初始化过程中,但已经被其他 Bean 需要时,Spring 会将 Bean 的一个早期引用放入二级缓存。这个早期引用是一个未完全初始化的 Bean 实例,但已经完成了实例化。
(3)三级缓存:singletonFactories
三级缓存是一个 ConcurrentHashMap
,用于存储 Bean 的对象工厂(ObjectFactory
)。当 Bean 还在初始化过程中,Spring 会将一个 ObjectFactory
放入三级缓存。ObjectFactory
是一个延迟加载的工厂,可以在需要时创建 Bean 的实例。
3. 单例 Bean 循环依赖的处理流程
假设我们有两个单例 Bean:BeanA 和 BeanB,它们通过 setter 方法相互依赖。以下是 Spring 处理循环依赖的流程:
(1)实例化 BeanA
Spring 开始实例化 BeanA,创建 BeanA 的实例。
(2)将 BeanA 放入三级缓存
Spring 将 BeanA 的 ObjectFactory
放入三级缓存 singletonFactories
。
(3)注入依赖
Spring 开始注入 BeanA 的依赖,发现 BeanA 依赖 BeanB。
(4)实例化 BeanB
Spring 开始实例化 BeanB,创建 BeanB 的实例。
(5)将 BeanB 放入三级缓存
Spring 将 BeanB 的 ObjectFactory
放入三级缓存 singletonFactories
。
(6)注入依赖(循环依赖出现)
Spring 开始注入 BeanB 的依赖,发现 BeanB 依赖 BeanA。此时,BeanA 还在初始化过程中,但已经在三级缓存中。
(7)从三级缓存获取 BeanA
Spring 从三级缓存 singletonFactories
中获取 BeanA 的 ObjectFactory
,并调用 ObjectFactory.getObject()
方法获取 BeanA 的早期引用。
(8)将 BeanA 的早期引用放入二级缓存
Spring 将 BeanA 的早期引用放入二级缓存 earlySingletonObjects
。
(9)完成 BeanB 的依赖注入
Spring 使用 BeanA 的早期引用完成 BeanB 的依赖注入。
(10)完成 BeanB 的初始化
Spring 完成 BeanB 的初始化,将 BeanB 放入一级缓存 singletonObjects
。
(11)完成 BeanA 的依赖注入
Spring 使用 BeanB 的实例完成 BeanA 的依赖注入。
(12)完成 BeanA 的初始化
Spring 完成 BeanA 的初始化,将 BeanA 放入一级缓存 singletonObjects
。
(13)清理二级和三级缓存
Spring 清理二级缓存和三级缓存中与 BeanA 和 BeanB 相关的条目。
4. 总结
Spring 通过三级缓存机制解决了单例 Bean 的 setter 循环依赖问题。具体来说:
- 一级缓存存储完全初始化完成的 Bean。
- 二级缓存存储提前曝光的 Bean 的早期引用。
- 三级缓存存储 Bean 的对象工厂,用于延迟加载。
通过这种机制,Spring 能够在 Bean 还在初始化过程中时,提供一个早期引用,从而解决循环依赖问题。然而,Spring 不支持构造器循环依赖和原型 Bean 的循环依赖,因为它们的处理逻辑更为复杂,且不符合 Spring 的设计原则。
希望以上内容对你理解 Spring 循环依赖的处理原理有所帮助!