
先给结论再拆过程面试官问这个问题想考察的不是记忆力而是你对Bean生命周期和AOP代理机制的理解。回答时先给一句精准的结论Spring通过三级缓存和提前暴露未完全初始化的对象引用来解决单例作用域Bean的循环依赖问题。紧接着补充边界条件——构造器注入的循环依赖Spring无法解决会直接抛出BeanCurrentlyInCreationException。先说清楚能解决什么、不能解决什么面试官立刻知道你是有实战认知的。三级缓存到底存了什么三个缓存的分工需要说清楚。一级缓存singletonObjects存的是完全初始化好的成熟Bean二级缓存earlySingletonObjects存的是已实例化但未完成属性注入的半成品Bean三级缓存singletonFactories存的是ObjectFactory工厂对象用于在需要时生成早期引用。核心在于实例化之后、属性注入之前这个窗口期。Bean刚被new出来属性还是空的Spring立刻把它包装成ObjectFactory放进三级缓存。这一步叫做“提前暴露”。如果A依赖B、B又依赖A当B需要注入A时Spring从三级缓存拿到工厂对象调用getObject()拿到A的早期引用B就能顺利完成创建回头再回填给A。为什么三级不够必须要有第三级这是面试中最能拉开差距的追问。很多候选人会说“二级缓存就够了”这个回答直接暴露了对AOP机制的理解盲区。二级缓存确实可以解决纯粹的循环依赖但它解决不了循环依赖遇上AOP代理的场景。如果一个Bean需要被AOP增强正常情况下代理对象是在初始化后由BeanPostProcessor生成的。但如果发生循环依赖B在注入A的时候A还没有走到初始化后这一步。这时候B拿到的应该是原始对象还是代理对象答案必须是代理对象否则B持有的A就绕过了切面逻辑事务、日志全部失效。三级缓存放的是工厂而非直接的对象就是为了把“是否需要代理”这个判断延迟到真正被依赖时才执行。工厂里的getEarlyBeanReference()方法会在被调用时检查这个Bean是否需要AOP代理如果需要就提前生成代理对象返回不需要就返回原始对象。如果只用二级缓存代理对象的生成时机和单例一致性都无法保证多个地方注入的可能是不同地址的对象直接破坏单例原则。缓存之间的流转逻辑A实例化后ObjectFactory进入三级缓存。B创建时注入A依次查一级、二级、三级缓存在三级中命中工厂并调用getObject()拿到A的早期引用可能是代理对象同时将这个引用升级到二级缓存从三级缓存中移除。A完成初始化后最终进入一级缓存同时清理二级缓存中的临时引用。被追问时的应对面试官如果追问“Lazy注解和三级缓存是什么关系”可以点明Lazy是另一条路——它通过延迟代理对象的创建来打破循环本质上是把依赖注入推迟到真正使用时而不是在容器启动阶段就暴露半成品。两种方案解决的问题相同但机制完全不同。如果面试官问“原型作用域的Bean能不能解决”答案是不能因为原型Bean不走单例缓存每次getBean都是新对象三级缓存机制根本不适用。把这套逻辑讲完面试官看到的不只是你背过八股文而是你真的理解Spring在对象完整性和灵活性之间做的架构权衡。三级缓存的设计本质是在“Bean必须完整才能使用”这个原则和“循环依赖导致谁都无法先完整”这个死结之间找到了一条可行的中间路径。