Spring的循环依赖问题
出现原因
一个类初始化的时候需要另一个类的实例,反过来也是
框架怎么解决的
spring默认是支持自动解决单例的循环依赖问题的
关键在于提前暴露:在A实例化完但是还没依赖注入(属性填充)的时候,直接把A暴露出来给B用
三级缓存
IOC容器里面维护了三个Map存储不同阶段的Bean实例
| Map名称 | 存储内容 | 作用 | |
|---|---|---|---|
| 一级缓存 | singletonObjects | 存储完整、初始化完毕的单例Bean。(bean) | 正常的Bean容器。 |
| 二级缓存 | earlySingletonObjects | 存储提前暴露的Bean实例,可能是代理对象或原始对象。(Bean) | 存放已暴露但尚未完全初始化的Bean。 |
| 三级缓存 | singletonFactories | 存储一个工厂对象 (逻辑) | 负责判断要不要代理并且返回给二级缓存实例 |
- A开始创建
- (A到3级)暴露A的工厂:实例化A但是在DI之前,把ObjectFactory放到三级缓存里,来负责A的早期引用
- A的属性注入: A开始进行属性注入,发现依赖B,于是开始查找B。
- B开始创建
- B的属性注入: B开始进行属性注入,发现依赖A,于是开始查找A。
- 查找A(三级缓存触发): B尝试从一级缓存、二级缓存查找A,都未找到。它会访问三级缓存,发现A对应的
ObjectFactory。 (A到2级)工厂执行: B执行这个
ObjectFactory。Factory会判断:A是否需要AOP代理?- 如果需要代理,Factory会生成A的代理对象。
- 如果不需要代理,Factory直接返回A的原始对象。
- 无论返回什么, 这个对象都会被放入二级缓存 ,并从三级缓存中移除。
- (B从2级拿的A)B获取A并初始化: B拿到A的半成品,完成自己的属性注入,并完成初始化。此时B是一个完整的Bean。
- A完成初始化: B完成后,Spring回到A的初始化流程。A拿到完整的B,完成自己的属性注入。
- (A到1级)A最终入库: A完成所有初始化步骤(包括
init方法、AOP代理创建等),最终将完整的A放入一级缓存 (singletonObjects),并从二级缓存中移除。
总结: 第三级缓存 (singletonFactories) 是一个缓冲层,它将生成早期Bean实例的决定权延迟到了真正需要解决循环依赖的那一刻,从而确保了AOP代理的正确性和完整性。
情况总结
| 场景分类 | AOP 需求 | 注入方式 | 循环依赖 | 最少所需的缓存级别 | 原因 |
|---|---|---|---|---|---|
| 正常 Bean 创建 | 有/无 | - | 无问题 | 一级缓存 | 仅需最终存储完整的 Bean 实例。 |
| 循环依赖 (简单) | 均无 AOP | Setter/Field | 能解决 | 二级缓存 | 仅需提前暴露原始实例,无需 AOP 决策逻辑。 |
| 循环依赖 (复杂) | 至少一方有 AOP | Setter/Field | 能解决 | 三级缓存 | 需要 ObjectFactory 延迟 AOP 代理的生成和决策,确保早期引用是 AOP 后的对象。 |
| 循环依赖 (无效) | 有/无 | Constructor | 不能解 | -- | 无法在实例化前提前暴露实例。 |
框架不能解决的
构造器互相注入的
为什么?因为构造器创建bean的时候DI和实例化的是一个步骤(调用构造器方法)
过程:尝试实例化 A 时发现需要 B,转而实例化 B;实例化 B 时发现需要 A,又转而实例化 A。死锁
解决:a.改为Setter 注入或者@Resource注解 b.在其中一个Bean对象的构造函数参数上使用@Lazy
Prototype作用域的
为什么?三级缓存是为单例模式设计的
过程:同上
解决:a.改为单例 b.手动模拟三级缓存
注入ObjectProvider<B>或Provider<B>,而非 B 实例。在需要 B 时,手动调用getObject()或get()获取新的原型实例。
