"Spring 是怎么解决循环依赖的?三级缓存到底是干嘛的?能不能只用两级?"
今天用大白话 + 真实执行顺序 + 代码级拆解,彻底把这事儿掰开揉碎讲清楚。读完你不仅能答面试,还能知道什么时候循环依赖会失败、什么时候可以干掉三级缓存。
一、先说结论
| 问题 | 答案 |
|---|---|
| Spring 能解决循环依赖吗? | 能!但仅限 setter 注入 + 单例 + 非 AOP 动态代理的情况 |
| 必须三级缓存吗? | 不必须!两级缓存就够了,第三级是为了"提前曝光动态代理对象" |
| 能不能关三级缓存? | 可以,加 spring.main.allow-circular-references=true 后可降级为二级 |
二、循环依赖到底长啥样?
java
@Component
class A {
@Autowired
private B b;
}
@Component
class B {
@Autowired
private A a;
}
A 要 B,B 要 A,互相等着对方先创建完 → 死锁?
三、三级缓存到底是啥?
java
// Spring 容器内部维护了三个 Map
private final Map<String, Object> singletonObjects = new HashMap<>(); // 一级缓存:成品单例
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(); // 三级缓存:对象工厂(最关键!)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(); // 二级缓存:提前暴露的半成品
四、完整创建流程时间线(A → B → A)

| 步骤 | 正在创建 | 操作 | 放进了哪个缓存 |
|---|---|---|---|
| 1 | A | doGetBean("a") → 开始创建 A | |
| 2 | A | createBean → 实例化(new A()) | |
| 3 | A | 把"能造A的工厂"扔进三级缓存(提前曝光) | → singletonFactories |
| 4 | A | 正在给 A 注入属性 → 发现需要 B → 暂停 A,去创建 B | |
| 5 | B | 同理实例化 B → 把"能造B的工厂"扔进三级缓存 | → singletonFactories |
| 6 | B | 给 B 注入属性 → 发现需要 A → 去容器里找 A | |
| 7 | B | 先查一级缓存 → 没有 查二级缓存 → 没有 查三级缓存 → 拿到 A 的工厂! | |
| 8 | B | 执行 factory.getObject() → 真正触发 A 的动态代理(如果需要) | → earlySingletonObjects(二级) |
| 9 | B | B 拿到"半成品 A"注入成功 → B 创建完成 → 放入一级缓存 | → singletonObjects |
| 10 | A | 回到 A 的注入 → 拿到已经完成的 B → A 也创建完成 → 放入一级缓存 | → singletonObjects |
关键点:B 在创建过程中通过三级缓存拿到了"还在创建中的 A"的早期引用,从而打破循环。
五、三级缓存到底哪级最重要?
答案是:第三级 singletonFactories
它存的是 ObjectFactory<?>,作用是"允许在对象完全初始化前就暴露一个可以生成代理对象的工厂"。
为什么需要这一层?因为如果 A 需要被 AOP 代理(@Async、@Transactional 等),代理对象必须在属性注入阶段就得准备好。如果只暴露原始对象,注入给 B 的是原生 A,后面再代理就晚了,B 拿到的永远不是代理对象。
补充:第一级缓存就是填充完整的A,第二级缓存就是未填充B的A;(比较好理解不过多解释)
关键源码:
java
// AbstractAutowireCapableBeanFactory.java (Spring 源码)
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 1. 创建 A 的原始对象(new A())
Object beanInstance = createBeanInstance(beanName, mbd, args).getWrappedInstance();
// 2. 判断是否需要提前暴露(单例 + 允许循环依赖 + 正在创建)
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// ✅ 3. 把一个 Lambda 表达式 放入三级缓存!
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
}
// 4. 继续注入属性(这时会去创建 B)
populateBean(beanName, mbd, beanInstance);
// ...
}
addSingletonFactory做了什么?
java
// DefaultSingletonBeanRegistry.java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// ✅ 三级缓存:Map<String, ObjectFactory<?>>
this.singletonFactories.put(beanName, singletonFactory); // ← Lambda 被存进去了!
}
}
}
这段Lambda怎么被拿出来的,做了什么?
java
// DefaultSingletonBeanRegistry.java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 一级缓存没找到(A 还没初始化完)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 2. 二级缓存也没找到
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// ✅ 3. 从三级缓存取出 Lambda!
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// ✅ 4. 调用 getObject() → 执行 Lambda!
singletonObject = singletonFactory.getObject(); // ← 生成早期引用
// ✅ 5. 放进二级缓存(升级!)
this.earlySingletonObjects.put(beanName, singletonObject);
// ✅ 6. 移除三级缓存(Lambda 只用一次)
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
getObject()的时候,Lambda被执行。
java
// 就是之前放进去的 Lambda:
() -> getEarlyBeanReference("a", mbd, rawA)
// getEarlyBeanReference 会判断是否需要 AOP 代理
// 如果没有 AOP,直接返回 rawA(原始 A 对象)
// 如果有 @Transactional,就返回代理对象
六、哪些情况循环依赖会失败?
| 场景 | 是否能解决 | 原因 |
|---|---|---|
| setter 注入 + 单例 | 能 | 三级缓存完美解决 |
| 构造器注入 | 不能 | 实例化阶段就要对方,缓存里根本没有 |
| @Async / @Transactional 代理 | 能 | 三级缓存专门为动态代理设计 |
| prototype 原型作用域 | 不能 | 每次都新创建,没法提前暴露 |
| @Lazy 懒加载 | 能 | 实际注入的是代理对象 |
| 多例 + @Autowired | 不能 | 每次 getBean 都新创建,死循环 |
七、能不能只用二级缓存?
yaml
# application.yml
spring:
main:
allow-circular-references: true # 开启后,Spring 内部会降级用二级缓存
实测结果:普通 setter 循环依赖依然能解决,但一旦涉及 AOP 代理就会报错 BeanCurrentlyInCreationException
一句话总结:第三级缓存就是为了代理而生的,就是提前把产生代理对象的逻辑放在第三级缓存,如果需要代理就提前代理放进二级缓存。