循环依赖与三级缓存#
一句话答案#
Spring 三级缓存解决 setter 注入循环依赖:一级(成品 Bean)、二级(半成品)、三级(ObjectFactory,解决 AOP 代理)。
核心要点
三级缓存:
- singletonObjects(一级):完整初始化的Bean
- earlySingletonObjects(二级):提前曝光的半成品Bean
- singletonFactories(三级):Bean的ObjectFactory(解决AOP代理)
流程: A创建→放三级缓存→注入B→B创建→注入A(从三级升到二级)→B完成→A完成
不能解决: 构造器注入的循环依赖
深挖:为什么必须三级,二级不够?#
三级缓存各存什么(注意存的内容本质不同):
| 缓存 | 字段 | 存什么 | 何时放入 |
|---|---|---|---|
| 一级 | singletonObjects | 完整成品 Bean(已初始化、已是最终代理) | Bean 全部创建完成后 |
| 二级 | earlySingletonObjects | 提前暴露的早期引用(可能是半成品原始对象,也可能是提前生成的代理) | 三级工厂被调用后,把产物升到这里 |
| 三级 | singletonFactories | ObjectFactory(lambda),内部回调 getEarlyBeanReference | 实例化后、属性注入前 |
核心问题:为什么不直接在二级缓存里放代理对象?
关键在于「代理本该在初始化之后才生成」。正常生命周期里,代理是
AbstractAutoProxyCreator在postProcessAfterInitialization(初始化完成后)这一步才创建的。如果一个 Bean 根本没有循环依赖,它就该走正常流程、在初始化后才被代理——此时提前造代理是错误且浪费的。三级缓存用 ObjectFactory 把「是否提前生成代理」变成被动触发:只有当这个 Bean 真的被卷入循环依赖、被别的 Bean 提前引用时,才会有人来调这个工厂的
getObject(),进而触发getEarlyBeanReference提前生成代理。没发生循环依赖,工厂永远不被调用,代理仍按正常时机在初始化后生成。二级缓存是「存对象」,三级缓存是「存生成对象的时机」——这就是二级不够、必须三级的根因。
怎么保证注入出去的代理和最终代理是同一个?
- 三级工厂调用
getEarlyBeanReference提前造代理后,会把这个代理记进earlyProxyReferences(key 为 beanName)。 - 该 Bean 自己走完初始化、进入
postProcessAfterInitialization时,发现earlyProxyReferences里已有它,就直接返回那个已造好的代理,不再重复 wrap。 - 这样「提前注入给别人的代理」和「自己最终用的代理」是同一个对象,避免了双重代理或引用不一致。
为什么 @Async 的循环依赖仍然报错?
@Async的代理不走getEarlyBeanReference那条提前暴露的链路,它的代理只在初始化后由AsyncAnnotationBeanPostProcessor生成。于是在循环依赖中,别人提前拿到的是未被 @Async 代理的原始早期引用,而该 Bean 自己初始化后又生成了一个新代理——两者不是同一个对象。Spring 检测到「提前暴露的引用」与「最终 Bean」不一致,直接抛BeanCurrentlyInCreationException。这也是为什么普通 AOP(@Transactional)循环依赖能成而@Async不行的根本区别。
面试回答(2分钟版)
Spring通过三级缓存机制解决单例Bean的setter注入循环依赖问题。三级缓存分别是:singletonObjects一级缓存存放完整初始化的成品Bean,earlySingletonObjects二级缓存存放已实例化但未完成属性注入的半成品Bean,singletonFactories三级缓存存放Bean的ObjectFactory工厂。具体流程是这样的:假设A和B互相依赖,A创建实例后先把自己的ObjectFactory放入三级缓存,然后注入属性时发现需要B,于是去创建B。B创建过程中发现需要A,从三级缓存拿到A的ObjectFactory调用getObject获取A的早期引用,此时如果A需要AOP代理,ObjectFactory会提前生成代理对象放入二级缓存。B拿到A的引用后完成初始化,A再拿到完整的B完成自己的初始化。三级缓存的核心价值在于解决存在AOP代理时的循环依赖——如果只用两级缓存,无法在需要时提前创建代理对象。但三级缓存无法解决构造器注入的循环依赖,因为对象还没实例化根本无法放入任何缓存,这种情况需要用@Lazy注解注入代理对象来延迟初始化。
追问与易错
追问方向:
- “为什么需要三级缓存?两级不行吗?”→ 两级缓存能解决普通循环依赖,但无法处理有 AOP 代理的情况;三级缓存存 ObjectFactory 可以延迟决定是否创建代理对象,保证注入的是正确的(代理/非代理)引用
- “构造器注入的循环依赖能解决吗?”→ 不能,因为构造器注入在实例化阶段就需要依赖,此时对象还没创建完无法放入任何缓存;解决方案是改用 setter 注入或加 @Lazy 注解
- “@Lazy 怎么解决循环依赖的?”→ @Lazy 不立即创建真实依赖,而是注入一个 CGLIB 代理对象作为占位符,延迟到第一次实际调用方法时才触发真实 Bean 的初始化和注入
易错点:
- ❌ “三级缓存是为了解决循环依赖”——更准确说是为了解决「有 AOP 代理的循环依赖」
- ❌ “所有循环依赖都能解决”——构造器注入和 prototype 作用域的循环依赖无法解决