<a href="https://pan.baidu.com/s/1RUVf75gmDVsg8MQp4yRChg?pwd=9b3g" style="color: blue;">点击领取《2025年Java面试宝典》</a>
提取码: 9b3g

Spring循环依赖怎么解决的原理
什么是循环依赖?
咱们先举个实际例子:类A依赖类B,类B又反过来依赖类A。这种"你中有我,我中有你"的情况就像两个人同时找对方借钱,结果谁都拿不到钱。在Spring容器初始化Bean的时候,这种循环依赖会导致创建对象卡死。
Spring官方文档明确说过不支持构造器注入导致的循环依赖,但为什么用属性注入(@Autowired)时就能解决呢?这就要说到Spring的杀手锏——三级缓存机制。
三级缓存的设计思路
Spring解决循环依赖的核心是提前暴露半成品对象。想象一下造房子:工人刚打好地基(对象刚实例化),就先把门牌号登记在物业(缓存),等邻居来借工具时就能临时应付,后续再慢慢装修(属性填充)。三级缓存具体分为:
- 一级缓存(单例池):存放完整的Bean
- 二级缓存(earlySingletonObjects):存放提前暴露的原始对象
- 三级缓存(singletonFactories):存放生成代理对象的工厂

解决流程四步走
当面试被问到这个问题时,建议按这个脉络回答:
- 实例化A:通过反射new出A对象,此时A还是个"空壳"(属性未填充)
- 放入三级缓存:将A的ObjectFactory存入三级缓存,相当于给A贴了个临时标签
- 填充属性B:发现需要注入B,开始创建B的流程
- B需要注入A:此时从三级缓存拿到A的工厂,生成早期引用(可能是代理对象),最终完成B的创建
整个过程就像两个人跳探戈:A先迈半步,等B跟上节奏后,两人就能完美配合。这里有个重点——只有单例且采用属性注入的Bean才能解决循环依赖,原型(prototype)作用域的Bean会直接报错。
哪些场景会失效?
虽然三级缓存很强大,但要注意三个雷区:
- 构造器注入循环依赖:还没生成对象就要注入,死锁不可避免
- @Async注解的Bean:因为代理对象创建时机不同
- 自定义BeanPostProcessor:如果修改了Bean的初始化流程

如何规避循环依赖?
虽然Spring提供了解决方案,但咱们还是要尽量从设计层面避免。推荐两种方法:
- 应用分层:Controller->Service->DAO的经典结构
- 依赖倒置:通过接口解耦具体实现
需要购买面试鸭会员的同学,通过面试鸭返利网找我可返25元。最后再强调下,理解三级缓存机制不仅要懂流程,更要明白Spring这样设计背后的权衡——用空间换时间,在性能和复杂度之间找到平衡点。


