什么是 Spring 的三级缓存?
结论先行:三级缓存是 Spring 为解决单例 Bean 的 setter/字段注入循环依赖而在 DefaultSingletonBeanRegistry 中维护的三个 Map:singletonObjects(一级,成品)、earlySingletonObjects(二级,早期引用)、singletonFactories(三级,对象工厂)。核心手法是"提前暴露引用":Bean 还没初始化完,就先把它的引用(必要时是代理)交出去,让依赖方先完成装配。三级而非两级,是为了让 AOP 代理延迟生成且只生成一次。
一、三个 Map
| 级别 | 字段 | 存什么 | 何时放入 | 何时移除 |
|---|---|---|---|---|
| 一级 | singletonObjects | 完全初始化好的单例 | addSingleton(初始化完成后) | 一般不删,容器销毁时清 |
| 二级 | earlySingletonObjects | 已曝光但未初始化的早期引用(可能是代理) | getSingleton 从三级取出后 | 放入一级时移除 |
| 三级 | singletonFactories | ObjectFactory,调用 getEarlyBeanReference | doCreateBean 属性填充前 | 被二级取走后移除 |
二、时序
① getBean(A)
└─ doCreateBean(A):new A(),属性填充发现依赖 B
② getBean(B)
└─ doCreateBean(B):new B(),属性填充发现依赖 A
③ getSingleton(A, true)
一级无 → 二级无 → 三级有 A 的 ObjectFactory
└─ factory.getEarlyBeanReference() → 可能返回 A 的代理
└─ 放入二级,移除三级
④ B 拿到 A 的早期引用,完成初始化 → 放入一级
⑤ A 拿到 B,完成属性填充与初始化
└─ 最终放入一级时,若二级里是代理则直接复用,避免重复代理
三、关键源码骨架
// 1. 创建 Bean 时,属性填充之前把 ObjectFactory 放进三级缓存
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
Object bean = createBeanInstance(mbd, beanName, args); // 实例化
boolean earlyExposure = mbd.isSingleton() && this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName);
if (earlyExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
populateBean(beanName, mbd, bean); // 属性填充(可能触发循环)
Object exposed = initializeBean(beanName, bean, mbd); // 初始化 + AOP 代理
addSingleton(beanName, exposed);
return exposed;
}
// 2. 取单例:依次查三级 → 二级 → 一级
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singleton = singletonObjects.get(beanName); // 一级
if (singleton == null && isSingletonCurrentlyInCreation(beanName)) {
singleton = earlySingletonObjects.get(beanName); // 二级
if (singleton == null && allowEarlyReference) {
ObjectFactory<?> f = singletonFactories.get(beanName); // 三级
if (f != null) {
singleton = f.getObject(); // 生成/取代理
earlySingletonObjects.put(beanName, singleton);
singletonFactories.remove(beanName);
}
}
}
return singleton;
}
// 提前暴露的前提:该单例"正在创建中"
protected boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
// beforeSingletonCreation 加入、afterSingletonCreation 移除,
// 所以正常 Bean 从不走 earlyReference 分支,三级缓存几乎零成本。
四、为什么必须是三级
- 如果只有一级:未初始化的对象会污染缓存,别处拿到半成品。
- 如果只有两级(成品 + 早期引用):早期引用必须在"实例化后立刻"生成,此时
BeanPostProcessor还没跑,AOP 代理无从生成;若等到需要时再生成,又可能被多次调用产生多个代理。 - 三级的价值:放的是
ObjectFactory而不是实例,按需调用。正常无循环的 Bean 从不调用它,代理只在真正发生循环时才提前生成,且同一 Bean 只生成一次(二级缓存去重)。
五、常见追问
- 问:为什么构造器注入的循环依赖解决不了? 构造器注入要求先有依赖实例才能
new,实例还没产生,无从"提前暴露引用"。 - 问:prototype 作用域能解决吗? 不能。容器不缓存 prototype,没有三级缓存可用,直接抛
BeanCurrentlyInCreationException。 - 问:
@Async代理的 Bean 循环依赖为何失败?@Async代理由AsyncAnnotationBeanPostProcessor在初始化后创建,而三级缓存暴露的是初始化前的引用,两边可能生成不同对象,Spring 会检测到并报错。 - 问:Spring Boot 2.6 之后为什么默认禁止循环依赖?
spring.main.allow-circular-references=false,鼓励用分层重构、@Lazy、事件解耦替代隐式循环。
小结
三级缓存 = 成品缓存 + 早期引用缓存 + 对象工厂缓存,本质是"用延迟生成换取代理正确性"。答题顺序:三张 Map 各存什么 → 时序 → 为什么三级 → 哪些场景救不了。把"三级是为了 AOP 代理"这句话说出来,就和只背八股的答案拉开差距了。