[面试题] 什么是Spring的三级缓存?
📖 版块 A:知识讲解版 (温故知新)
-
三级缓存定义:Spring 在
DefaultSingletonBeanRegistry类中定义了三个 Map 来存储单例 Bean,统称为三级缓存。- 第一级缓存 (singletonObjects):正式曝光的成品池。存放已经完全初始化好的 Bean。
- 第二级缓存 (earlySingletonObjects):半成品池。存放提前暴露出来的、尚未填充属性的 Bean。
- 第三级缓存 (singletonFactories):工厂池。存放 Bean 工厂(ObjectFactory),用于解决 AOP 情况下的循环依赖。
-
核心流程:
- 当 A 依赖 B,B 又依赖 A 时,Spring 在实例化 A 后,先将 A 的 ObjectFactory 放入三级缓存。
- 接着填充 B,发现 B 依赖 A,去缓存找:一级没有,二级没有,三级命中。
- 通过三级缓存的工厂获取 A(如果是 AOP 则返回代理对象),放入二级缓存,并删除三级。
- B 完成初始化进入一级缓存,回到 A 的流程,A 从二级缓存拿到引用,完成初始化进入一级缓存。
-
局限性:
- 只支持单例(Singleton)作用域。
- 只支持 Setter 注入(或字段注入),不支持构造器注入产生的循环依赖。
🎤 版块 B:面试实战版 (高手逐字稿)
Q:什么是 Spring 的三级缓存?
💬 高手逐字稿:
Spring 的三级缓存是用来解决单例 Bean 之间循环依赖问题的核心机制。
首先我先明确这三级分别是什么:第一级是 singletonObjects,存的是完全初始化好的成品 Bean;第二级是 earlySingletonObjects,存的是刚实例化但还没注入属性的半成品 Bean;第三级最关键,是 singletonFactories,存的是生成 Bean 的工厂对象。
关于为什么是三级,我可以结合 AOP 场景拆解一下它的核心逻辑:
假设 A 和 B 互相依赖。Spring 创建 A 时,会先把 A 的工厂对象放入第三级缓存。接着 A 填充属性发现依赖 B,于是去创建 B。B 初始化时又发现依赖 A,这时候 B 会去查缓存。它在三级缓存里找到了 A 的工厂,通过工厂拿到了 A 的引用,并把这个引用放到二级缓存里。B 拿到 A 之后顺利完成初始化,进入一级缓存。最后 A 拿到 B 的引用,也完成初始化进入一级缓存。
这里经常会有一个深度考点:为什么不能只用二级缓存?
我认为这是 Spring 为了兼顾“性能”和“AOP 代理”做的权衡。如果没有 AOP,二级缓存确实够了。但如果有 AOP,按照 Spring 的生命周期,代理对象是在初始化后期才生成的。如果没有第三级缓存的工厂,我们就必须在 Bean 实例化后立刻创建代理对象。这违背了 Spring 的设计原则,因为 Spring 希望 Bean 在完全初始化后再决定是否生成代理。
通过第三级缓存,Spring 实现了“按需延迟创建代理”:只有当发生循环依赖时,才会通过工厂提前创建代理对象并放入二级缓存;如果没有循环依赖,代理还是在最后一步生成。
高手自问自答: 这里还有一个细节,三级缓存能解决所有的循环依赖吗?答案是否定的。它不能解决构造器注入引发的循环依赖。因为构造器注入发生在 Bean 实例化阶段,这时候 Bean 连工厂都还没来得及放入第三级缓存,所以会直接抛出 BeanCurrentlyInCreationException。这种情况下,通常需要通过 @Lazy 注解或者改用 Setter 注入来规避。