☰
面试官问Spring循环依赖,我这样答当场通过
2026/9/30 7:25:19 网站建设 项目流程

先给结论,再拆过程

面试官问这个问题,想考察的不是记忆力,而是你对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必须完整才能使用”这个原则和“循环依赖导致谁都无法先完整”这个死结之间,找到了一条可行的中间路径。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询