Spring 用三级缓存解决单例 Bean 的循环依赖,本质是:
在 Bean 还没完全初始化完成时,先把“半成品对象的引用”提前暴露出去,让另一个 Bean 能注入进来,从而打破死循环。
一、先说结论(一句话版)
Spring 通过三级缓存:
- 提前暴露“早期对象引用”
- 允许依赖方先拿到代理或原始对象
- 等双方都创建完成后再完成属性填充与初始化
从而解决:
A 依赖 B,B 又依赖 A 的死循环问题(仅限单例、setter/字段注入)。
二、三级缓存分别是什么?
在 DefaultSingletonBeanRegistry 里:
// 一级缓存:完整成品 Bean
Map<String, Object> singletonObjects
// 二级缓存:提前曝光的半成品 Bean(已实例化,未初始化)
Map<String, Object> earlySingletonObjects
// 三级缓存:对象工厂(用于生成早期引用,支持 AOP 代理)
Map<String, ObjectFactory<?>> singletonFactories
三、循环依赖场景模拟
class A {
@Autowired B b;
}
class B {
@Autowired A a;
}
创建顺序假设是 A → B:
第一步:创建 A(未完成)
- 实例化 A(new A())
- 放入 三级缓存:
singletonFactories["A"] = () -> getEarlyBeanReference(A)
此时 A 还没注入 B,也没执行初始化。
第二步:A 需要注入 B → 开始创建 B
- 实例化 B
- B 需要注入 A → 去容器取 A
查缓存顺序:
1. 一级缓存(没有,A 还没完成)
2. 二级缓存(没有)
3. 三级缓存(有 A 的 ObjectFactory)
- 通过 ObjectFactory 拿到 A 的早期引用(可能是代理)
- 放入 二级缓存 earlySingletonObjects
- 注入到 B 中
- B 初始化完成 → 放入一级缓存
第三步:回到 A,继续完成注入
- 把完整的 B 注入到 A
- A 初始化完成
- A 从二级缓存升级到一级缓存
- 清除二、三级缓存中的 A
循环打破。
四、为什么要“三级”,不能两级吗?
关键在 AOP 代理。
如果只有两级:
- 只能提前暴露原始对象
- 但 AOP 需要的是 代理对象
三级缓存存的是:
ObjectFactory -> 生成“可能被增强过的早期引用”
调用:
getEarlyBeanReference(beanName, rawBean)
如果 A 需要被代理(@Transactional, @Async 等),
这里会提前返回 代理对象,保证:
依赖方注入的是“最终一致的代理对象”,而不是裸对象。
五、能解决哪些循环依赖?不能解决哪些?
能解决的(经典面试点)
| 场景 | 是否支持 |
|---|---|
| 单例 + setter/字段注入 | ✅ |
| 单例 + AOP 代理 | ✅(三级缓存) |
| 构造器循环依赖 | ❌ |
| 原型(prototype)循环依赖 | ❌ |
构造器为什么不行?
因为构造器阶段:
对象都还没 new 完,没法提前暴露引用
还没实例化就要对方实例,直接死锁。
六、面试标准回答模板
Spring 通过三级缓存解决单例 Bean 的循环依赖:一级缓存存放完全初始化好的 Bean,二级缓存存放提前曝光的半成品 Bean,三级缓存存放用于生成早期引用的 ObjectFactory。在创建 Bean A 时,会先将其 ObjectFactory 放入三级缓存;当 B 依赖 A 时,可通过三级缓存获取 A 的早期引用并注入,从而完成 B 的初始化;待 A 完成属性注入和初始化后,再升级到一级缓存。三级缓存的存在还能保证在循环依赖场景下正确暴露 AOP 代理对象。