Spring三级缓存
阅读提示
这页建议按“循环依赖是什么 -> 三级缓存是什么 -> 三级缓存的设计理由”顺序复习。
面试时优先说主流程,再补 AOP 代理和边界条件,答案会更完整。
本文以 Spring Framework 6.2.0 的单例创建源码为准,讨论普通 Bean 的字段 / Setter 注入循环,且前提是允许循环引用。Framework 的 AbstractAutowireCapableBeanFactory 默认允许尝试解决循环引用;Spring Boot 从 2.6 起默认禁止循环引用,不能把框架的处理能力理解成 Boot 应用默认可启动。
Boot 2.6 的 spring.main.allow-circular-references=true 可恢复这种尝试,但不能解决所有闭环。应优先拆分职责或调整依赖方向。参见 Boot 2.6 官方发布说明。
1、什么是循环依赖?
回答提示:
- A 依赖 B,B 又依赖 A。
- 在 Spring Bean 创建时会造成“对象还没创建完就被别人依赖”的问题。
参考回答:
- 循环依赖本质是依赖闭环;在允许且能够提前暴露实例的场景,Spring 可用早期引用完成依赖装配,否则会报创建异常。
2、Bean 创建的三个阶段
- 实例化(通过构造器或工厂方法等获得对象;此时构造器参数必须已能解析)。
- 属性填充(依赖注入)。
- 初始化(执行初始化逻辑、后置处理器)。
3、三级缓存分别存什么?
- 一级缓存
singletonObjects:正常创建流程中已完成初始化的单例引用,可能是代理。手工注册单例不由该缓存保证初始化。 - 二级缓存
earlySingletonObjects:已经取得的早期引用,可能是原始对象,也可能是代理;其目标对象尚未完成初始化。 - 三级缓存
singletonFactories:ObjectFactory<?>,用于按需取得早期引用,不是重新实例化 Bean 的工厂,也不是FactoryBean。
4、三级缓存如何解决循环依赖?
回答提示:
- A 实例化后,在单例、允许循环引用且正在创建的条件下,将取得早期引用的工厂放入三级缓存。
- 创建 B 时发现依赖 A,从三级缓存拿到 A 的早期引用。
- B 完成初始化后回填给 A,最后 A 完成初始化。
参考回答:
- 通过工厂延迟取得早期引用,再缓存这一引用,让后续依赖方复用。
以先创建 A、A 和 B 相互通过属性注入为例(示意省略锁和异常处理):
实例化 A → 注册三级工厂 () -> getEarlyBeanReference(A)
填充 A.b → 实例化 B → 填充 B.a
查 A:一级未命中 → 二级未命中 → 调用三级工厂
将 A 的早期引用放入二级,移除 A 的三级工厂
B 初始化完成 → B 进入一级 → 注入 A.b
A 初始化完成 → 必要时复用二级中的早期引用
A 进入一级,清除 A 的二级和三级条目这不是每个 Bean 都必经的“三级逐层升级”:没有人请求 A 的早期引用时,三级工厂不会被调用,也不会产生二级条目。查找早期引用还受“正在创建”和 allowEarlyReference 等条件约束。具体见官方源码 DefaultSingletonBeanRegistry#getSingleton / addSingleton 与 AbstractAutowireCapableBeanFactory#doCreateBean。
5、为什么不能只用二级缓存?
回答提示:
- “二级缓存只能存原始对象,不能存代理”并不准确;缓存本身当然可以存代理。
- 只考虑无代理的属性注入闭环,缓存原始对象即可打破闭环;即使有代理,也可以设计为实例化后立即生成并缓存早期代理,但这会改变代理创建时机。
- Spring 的三级工厂将早期引用的处理延迟到真正发生回查时,二级缓存则复用处理结果,避免反复取得不同的早期引用。
参考回答:
- 三级不是解决循环依赖的理论最少层数,而是 Spring 为按需提前暴露引用、配合后置处理器所采用的实现设计。
6、三级缓存和 AOP 的关系
- 工厂调用
getEarlyBeanReference,由SmartInstantiationAwareBeanPostProcessor参与处理;标准自动代理创建器AbstractAutoProxyCreator可在这里返回代理。 - 自动代理创建器记录早期处理过的对象,正常初始化后避免重复代理;Bean 工厂在最终暴露时复用早期引用,保持依赖方与容器取得的引用一致。
- 代理指向的目标仍可能未完成属性填充或初始化,不能据此在构造器、初始化回调中随意调用对方业务方法。
- 并非所有后置处理器都支持提前代理。如果先注入原始引用、后来又包装成不同对象,默认配置下容器可能因引用不一致而抛出
BeanCurrentlyInCreationException,不能笼统承诺“AOP 加循环依赖一定成功”。
参见 AbstractAutoProxyCreator#getEarlyBeanReference / postProcessAfterInitialization 官方源码。
7、循环依赖的边界条件(常见追问)
- 三级缓存主要支持允许循环引用时、可提前暴露实例的单例 Bean 属性注入闭环,不能保证所有此类闭环都成功。
- A、B 互相直接要求对方作为构造器参数时,实例还未产生,无法注册早期工厂,因此不能用这套机制解决。混合构造器和属性注入的闭环还可能受创建顺序影响。
- 原型 Bean 不走单例缓存体系,也不适用这套机制。
官方 依赖注入文档的 Circular dependencies 小节 说明了构造器闭环的异常与 Setter 注入的早期引用风险。
8、面试表达模板
可直接复述:
- 在允许循环引用的前提下,Spring 可通过三级缓存处理部分单例 Bean 属性注入闭环;Boot 从 2.6 起默认禁止循环引用。
- 一级放成品、二级放早期对象、三级放工厂,核心价值是支持代理对象按需提前暴露。
- 三级缓存支持按需取得早期引用,二级缓存复用该引用;三级并不是解决闭环的理论最少层数。
