☰
Spring 循环依赖是怎么解决的?三级缓存一次讲清楚
2026/9/27 4:33:17 网站建设 项目流程

Spring 循环依赖是面试里非常常见的问题。很多文章一上来就背“一级缓存、二级缓存、三级缓存”,但真正理解它,其实只需要抓住一个核心:

Spring 解决循环依赖的关键,是提前暴露一个还没有完全初始化完成的 Bean 引用。

一、什么是循环依赖?

假设有两个 Bean,A 依赖 B,B 又依赖 A:

@ServicepublicclassA{@AutowiredprivateBb;}
@ServicepublicclassB{@AutowiredprivateAa;}

正常创建过程就会变成:

创建 A ↓ A 需要 B ↓ 创建 B ↓ B 又需要 A

如果 Spring 再重新创建 A,就会一直循环下去。

那 Spring 是怎么打破这个循环的呢?

关键在于:对象实例化完成之后,就已经存在了,只是属性还没有注入完成。

Spring 创建 Bean 大致可以分为:

实例化 → 属性填充 → 初始化

例如:

Aa=newA();

执行完这一句,A 对象其实已经存在,只不过此时:

A └── b = null

所以 Spring 可以先把这个“半成品 A”的引用暴露出去,让 B 先使用。


二、Spring 的三级缓存

Spring 为此使用了三级缓存:

缓存作用
singletonObjects一级缓存,保存已经初始化完成的单例 Bean
earlySingletonObjects二级缓存,保存已经提前暴露的 Bean 引用
singletonFactories三级缓存,保存用于生成提前引用的对象工厂ObjectFactory

可以简单记成:

一级:Bean 已经创建完成 二级:Bean 还没创建完成,但提前引用已经生成 三级:提前引用还没生成,但我知道怎么生成

三、A 和 B 的循环依赖到底怎么解决?

假设还是:

A → B B → A

Spring 首先创建 A:

Aa=newA();

A 实例化完成以后,并不会马上进入一级缓存,因为它还没有完成属性填充和初始化。

Spring 会先向三级缓存中放入一个对象工厂:

三级缓存: "A" → ObjectFactory

这个工厂以后可以用来获取 A 的提前引用。

接下来 Spring 给 A 注入 B,发现 B 还不存在,于是开始创建 B。

创建 B 的时候,又发现:

@AutowiredprivateAa;

B 需要 A。

Spring 此时去查找 A:

一级缓存 → 没有 二级缓存 → 没有 三级缓存 → 找到了 A 的 ObjectFactory

于是 Spring 调用这个对象工厂,得到 A 的提前引用:

ObjectFactory ↓ A 的提前引用

然后把这个提前引用放入二级缓存:

二级缓存: "A" → A 的提前引用

B 就可以拿着这个 A 完成自己的依赖注入和初始化。

B 创建完成以后进入一级缓存。

然后回到 A 的创建流程,此时 B 已经有了:

A └── b → B

A 也就可以继续完成初始化,最后进入一级缓存。

整个过程可以简化成:

创建 A ↓ 实例化 A ↓ 把 A 的对象工厂放入三级缓存 ↓ A 需要 B ↓ 创建 B ↓ B 需要 A ↓ 从三级缓存获取 A 的提前引用 ↓ 提前引用进入二级缓存 ↓ A 注入 B ↓ B 创建完成 ↓ A 创建完成 ↓ 进入一级缓存

四、为什么还需要第三级缓存?

看到这里可能会有一个疑问:

既然最终还是要把 A 提前暴露出去,为什么不直接把 A 放进二级缓存?

普通情况下确实可以。

问题主要出现在AOP场景。

例如:

@ServicepublicclassA{@Transactionalpublicvoidtest(){}}

因为存在@Transactional,最终 Spring 暴露出去的 A 很可能不是原始对象,而是一个代理对象:

AProxy ↓ 原始 A

如果 Spring 在 A 刚实例化时,就直接把原始 A 放到二级缓存:

B.a → 原始 A

但是 A 最终创建完成以后,Spring 又产生了代理对象:

Spring 容器 → AProxy

这时候就会变成:

B 持有:原始 A 容器持有:AProxy

这样 B 调用 A 时,就可能绕过 Spring 的 AOP 代理。

因此 Spring 没有直接缓存原始 A,而是在三级缓存中保存:

ObjectFactory

当真的发生循环依赖,需要提前获取 A 时,再通过:

getEarlyBeanReference()

获取 A 的提前引用。

如果不需要代理,可以返回原始 A;如果需要 AOP,则有机会提前返回代理对象。

所以三级缓存的重点并不是“多存了一层 Bean”,而是:

通过ObjectFactory延迟生成 Bean 的提前引用,从而让 Spring 有机会决定提前暴露原始对象还是代理对象。

第一次生成提前引用以后,再把它放到二级缓存,后面直接复用即可。


五、为什么构造器循环依赖解决不了?

如果使用构造器注入:

classA{publicA(Bb){}}
classB{publicB(Aa){}}

创建 A 时:

创建 A ↓ 必须先有 B ↓ 创建 B ↓ 必须先有 A

这时候最大的问题是:

Aa=newA(...);

这一句都还没有执行完成。

也就是说,A 对象本身还不存在,自然就没有所谓的“提前引用”可以暴露。

因此 Spring 的三级缓存机制主要能处理的是:

单例 Bean 的 setter / 字段注入循环依赖。

而典型的构造器循环依赖无法通过提前暴露引用来解决。


六、总结

Spring 解决循环依赖可以归纳成一句话:

利用 Bean 已经实例化、但尚未完成初始化的时间窗口,提前暴露 Bean 的引用。

三级缓存则可以理解成 Bean 创建过程中的三个状态:

三级缓存 ObjectFactory “我知道怎么生成提前引用” ↓ 二级缓存 Early Reference “提前引用已经生成” ↓ 一级缓存 Singleton Bean “Bean 已经完整创建”

所以面试时,与其死记三级缓存的名字,不如先记住:

循环依赖靠“提前暴露引用”解决,三级缓存则负责管理这个提前引用从“准备生成”到“已经生成”,再到“完整 Bean”的整个过程。

另外需要注意:Spring 底层具备这套解决部分循环依赖的机制,并不代表实际项目应该依赖循环依赖。如果 A、B 长期互相依赖,很多时候也说明类之间的职责划分值得重新设计。

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

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

立即咨询