Spring 循环依赖是面试里非常常见的问题。很多文章一上来就背“一级缓存、二级缓存、三级缓存”但真正理解它其实只需要抓住一个核心Spring 解决循环依赖的关键是提前暴露一个还没有完全初始化完成的 Bean 引用。一、什么是循环依赖假设有两个 BeanA 依赖 BB 又依赖 AServicepublicclassA{AutowiredprivateBb;}ServicepublicclassB{AutowiredprivateAa;}正常创建过程就会变成创建 A ↓ A 需要 B ↓ 创建 B ↓ B 又需要 A如果 Spring 再重新创建 A就会一直循环下去。那 Spring 是怎么打破这个循环的呢关键在于对象实例化完成之后就已经存在了只是属性还没有注入完成。Spring 创建 Bean 大致可以分为实例化 → 属性填充 → 初始化例如AanewA();执行完这一句A 对象其实已经存在只不过此时A └── b null所以 Spring 可以先把这个“半成品 A”的引用暴露出去让 B 先使用。二、Spring 的三级缓存Spring 为此使用了三级缓存缓存作用singletonObjects一级缓存保存已经初始化完成的单例 BeanearlySingletonObjects二级缓存保存已经提前暴露的 Bean 引用singletonFactories三级缓存保存用于生成提前引用的对象工厂ObjectFactory可以简单记成一级Bean 已经创建完成 二级Bean 还没创建完成但提前引用已经生成 三级提前引用还没生成但我知道怎么生成三、A 和 B 的循环依赖到底怎么解决假设还是A → B B → ASpring 首先创建 AAanewA();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 → BA 也就可以继续完成初始化最后进入一级缓存。整个过程可以简化成创建 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这时候最大的问题是AanewA(...);这一句都还没有执行完成。也就是说A 对象本身还不存在自然就没有所谓的“提前引用”可以暴露。因此 Spring 的三级缓存机制主要能处理的是单例 Bean 的 setter / 字段注入循环依赖。而典型的构造器循环依赖无法通过提前暴露引用来解决。六、总结Spring 解决循环依赖可以归纳成一句话利用 Bean 已经实例化、但尚未完成初始化的时间窗口提前暴露 Bean 的引用。三级缓存则可以理解成 Bean 创建过程中的三个状态三级缓存 ObjectFactory “我知道怎么生成提前引用” ↓ 二级缓存 Early Reference “提前引用已经生成” ↓ 一级缓存 Singleton Bean “Bean 已经完整创建”所以面试时与其死记三级缓存的名字不如先记住循环依赖靠“提前暴露引用”解决三级缓存则负责管理这个提前引用从“准备生成”到“已经生成”再到“完整 Bean”的整个过程。另外需要注意Spring 底层具备这套解决部分循环依赖的机制并不代表实际项目应该依赖循环依赖。如果 A、B 长期互相依赖很多时候也说明类之间的职责划分值得重新设计。