Spring解决循环依赖的原理 - 面试高频题解析
作为一名Java开发者,在面试中被问及 Spring循环依赖 的原理是家常便饭。很多同学对这个概念一知半解,今天我们就以面试口述的形式,深入浅出地聊聊 Spring循环依赖 的核心解决机制。
🔥 2025最新Java面试宝典领取(限时福利):
<font color="blue">链接: https://pan.baidu.com/s/1RUVf75gmDVsg8MQp4yRChg?pwd=9b3g 提取码: 9b3g</font>
🔍 一、什么是循环依赖?
简单说,就是两个或多个Bean互相引用对方,形成了“鸡生蛋蛋生鸡”的死锁局面。比如:
@Service
public class AService {
@Autowired
private BService bService; // A 依赖 B
}
@Service
public class BService {
@Autowired
private AService aService; // B 又依赖 A
这种 循环依赖 场景在单例模式下,如果处理不当会导致容器启动失败。
🧠 二、Spring三级缓存:破解循环依赖的关键
Spring通过 三级缓存 机制巧妙解决了单例Bean的 循环依赖 问题。这三个缓存容器分别是:
-
singletonObjects(一级缓存)
存放 完全初始化 好的Bean实例。成品仓库。 -
earlySingletonObjects(二级缓存)
存放 提前暴露 的半成品Bean(仅实例化,未填充属性)。过渡区。 -
singletonFactories(三级缓存)
存放 Bean工厂对象(ObjectFactory),用于动态创建代理对象。生产线。

⚙️ 三、Spring解决循环依赖的流程拆解
假设A和B发生循环依赖,Spring容器启动时是这样处理的:
-
创建A实例(半成品)
- 调用A的构造器
new AService() - 将A的工厂对象放入 三级缓存(
singletonFactories)
- 调用A的构造器
-
填充A的属性(发现依赖B)
- 尝试从容器获取B实例
- B尚未创建 → 触发B的实例化流程
-
创建B实例(半成品)
- 调用B的构造器
new BService() - 将B的工厂对象放入 三级缓存
- 调用B的构造器
-
填充B的属性(发现依赖A!)
- 尝试获取A实例:
- 一级缓存? ✘
- 二级缓存? ✘
- 三级缓存 ✔ → 通过工厂提前暴露A的引用(可能是原始对象或代理)
- 将A放入 二级缓存,清空三级缓存
- 尝试获取A实例:
-
完成B的初始化
- B成功注入A的引用(半成品)
- B成为完整Bean → 移入 一级缓存
-
继续初始化A
- 从一级缓存获取B的成品 → 注入A
- A完成初始化 → 移入 一级缓存
经过这个过程,A和B这对互相依赖的对象成功完成初始化,循环依赖 被完美解决。
⚠️ 四、关键注意事项
-
只支持单例模式
原型Bean(prototype)的循环依赖会直接抛出BeanCurrentlyInCreationException。 -
构造器循环依赖无解
如果依赖通过构造器注入(而非Setter),三级缓存机制失效:public AService(BService bService) {...} // 构造器依赖此时Spring会检测到死循环并报错。
-
AOP代理的特殊处理
如果Bean需要被代理(如AOP切面),Spring会通过SmartInstantiationAwareBeanPostProcessor在三级缓存中提前生成代理对象(如下图流程):

💡 面试应答技巧
当面试官问:“Spring如何解决循环依赖?” 可以这样回答:
“Spring通过三级缓存机制解决单例Bean的循环依赖问题。核心是提前暴露半成品Bean:
- 一级缓存放成品Bean;
- 二级缓存放提前暴露的引用(防重复代理);
- 三级缓存存生成Bean的工厂。
当Bean A依赖B时,会先暂存A的半成品到三级缓存,等B创建时就能从缓存拿到A的引用。
注意构造器注入的循环依赖无法解决,原型Bean也不行。”
🉐 小贴士:需要系统刷Java面试题的同学,推荐通过 面试鸭返利网 购买面试鸭会员,可返利25元!海量真题解析和项目难点剖析一网打尽👇

理解 Spring循环依赖 的解决原理,不仅能应对面试,更能加深对IoC容器工作流程的掌握。遇到复杂依赖关系时,你就能快速定位问题根源。


