Spring 的三级缓存机制主要是为了解决 循环依赖 问题,同时保证 AOP 代理 的正确性。
结论是:两级缓存不够,必须用三级缓存。
一、Spring 三级缓存分别是什么
- 一级缓存 :
singletonObjects(已经完成初始化的单例 Bean) - 二级缓存 :
earlySingletonObjects(提前暴露的 Bean,尚未完全初始化,但已可被引用) - 三级缓存 :
singletonFactories(对象工厂 ObjectFactory,用于生成 Bean 的早期引用,通常是为了生成 AOP 代理)
二、为什么两级不够
1. 如果没有 AOP
理论上,仅为了解决循环依赖,二级缓存是足够的:
- A 创建时,实例化后放到二级缓存(或通过工厂暴露早期对象)
- B 依赖 A,从缓存拿到未完全初始化的 A
- B 完成初始化后,A 继续完成初始化
但问题出现在 A 需要被 AOP 代理 时。
2. 有 AOP 时的代理对象生成时机
Spring AOP 通常是在 Bean 初始化后 (postProcessAfterInitialization)通过后置处理器生成代理对象。
但在循环依赖中:
- 如果 B 依赖 A,A 必须提前暴露给 B
- 如果 A 最终要的是一个 代理对象,那么 B 依赖的也必须是同一个代理对象,而不是原始对象
- 若只用二级缓存,在 A 实例化后直接暴露原始对象,之后 A 初始化完成时生成代理,B 里持有的还是原始对象,就会出现 对象不一致 的问题
三、三级缓存如何解决
一、正常 Bean 的生命周期(无循环依赖)
- 实例化 (
createBeanInstance) → 原始对象 - 属性填充 (
populateBean) → 依赖注入 - 初始化 (
initializeBean):- 调用
applyBeanPostProcessorsBeforeInitialization - 调用
init-method - 调用
applyBeanPostProcessorsAfterInitialization
这一步中,AOP 后置处理器 会检查 Bean 是否需要代理,如果需要则生成代理对象,并返回代理。
- 调用
- 最终放入一级缓存 (
singletonObjects)
正常情况下,AOP 代理确实是在初始化完成后才创建的。
二、循环依赖时的特殊流程(A 需要代理,B 依赖 A)
假设 A 需要 AOP,B 依赖 A。
- A 实例化 :创建原始对象
A_raw。 - 提前暴露 :
addSingletonFactory将A_raw包装成一个ObjectFactory放入三级缓存。
这个工厂的getObject方法内部会调用getEarlyBeanReference(beanName, mbd, A_raw)。 - A 属性填充:发现需要 B → 去获取 B。
- B 开始创建 :实例化 B → 属性填充时发现需要 A → 从缓存获取 A。
- 一级缓存无(A 未完成)
- 二级缓存无
- 三级缓存有工厂 → 调用工厂 ,即执行
getEarlyBeanReference。
getEarlyBeanReference的作用 :
它会遍历所有SmartInstantiationAwareBeanPostProcessor(AOP 后置处理器实现了该接口),并调用其getEarlyBeanReference方法。
此时,AOP 后置处理器发现A_raw需要被代理(比如有@Transactional或@Aspect),就直接生成代理对象A_proxy并返回。- 返回的代理对象 :
- 存入二级缓存
earlySingletonObjects(key=beanName, value=代理对象) - 从三级缓存移除工厂
- 存入二级缓存
- B 获取到 A_proxy,完成 B 的创建。
- A 继续初始化 :
A 在完成属性填充后,进入初始化阶段。但在applyBeanPostProcessorsAfterInitialization中,AOP 后置处理器会检查该 Bean 是否已经存在代理(通过一个缓存标记),发现代理已经提前生成,就不会再生成新的代理,直接返回现有的代理对象。
最终 A 的代理对象被放入一级缓存。
为什么代理可以提前生成?
这是因为 SmartInstantiationAwareBeanPostProcessor 接口专门定义了一个 getEarlyBeanReference 方法,允许后置处理器在 Bean 实例化后、属性填充前 就提供一个对象引用(通常是代理)。
Spring AOP 的 AbstractAutoProxyCreator 实现了这个方法,它会在循环依赖发生时,提前创建代理,而不是等到初始化之后。
所以代理的创建时机有两个可能路径:
- 正常路径 :在
postProcessAfterInitialization中创建。 - 提前路径 :在
getEarlyBeanReference中创建(仅当发生循环依赖且需要提前暴露时)。
这样设计就是为了保证:即使循环依赖,依赖方拿到的也是最终要用的代理对象,而不是原始对象。
四、两级缓存能否模拟三级
如果强行用两级缓存,有两个方案,但都有问题:
-
实例化后立刻生成代理
会破坏 AOP 的正常顺序,有些后置处理可能依赖 Bean 初始化完成后的状态,提前代理会导致某些增强无法正确应用。
-
暴露时用原始对象,初始化后再替换
需要额外维护引用关系并更新所有依赖方,复杂度极高且容易出错。
三级缓存本质是 将"提前暴露"与"生成最终对象"解耦 ,通过 ObjectFactory 延迟决定早期引用是原始对象还是代理对象,兼顾了循环依赖和 AOP。
=