面试官:什么是Spring的三级缓存机制

Spring 的三级缓存机制是其解决循环依赖问题的核心技术,尤其在处理单例 Bean 时发挥关键作用。以下是对该机制的详细解释:

1. 三级缓存的定义

Spring 容器通过三个ConcurrentHashMap实现三级缓存:

  1. 一级缓存(singletonObjects)
    存储完全初始化且可用的单例 Bean(即已完成所有生命周期步骤的 Bean)。
  2. 二级缓存(singletonFactories)
    存储提前暴露的单例工厂对象,用于生成早期 Bean 引用(尚未完成属性注入和初始化)。
  3. 三级缓存(earlySingletonObjects)
    存储提前曝光的单例 Bean 实例(已创建但未完全初始化),通过工厂从二级缓存获取。

2. 为什么需要三级缓存?

循环依赖 指两个或多个 Bean 之间相互引用,形成闭环(例如 A 依赖 B,B 依赖 A)。若没有缓存机制,Spring 在创建 Bean 时会陷入无限循环。通过三级缓存,Spring 允许提前暴露未完成初始化的 Bean 引用,打破循环依赖。

3. 工作流程示例

假设存在循环依赖:A依赖BB依赖A。Spring 创建 Bean 的流程如下:

  1. 创建 A

    • A 开始创建,实例化后将其工厂(包含ObjectFactory回调)放入三级缓存
    • 准备注入 B 时,发现 B 未创建,暂停 A 的创建。
  2. 创建 B

    • B 开始创建,实例化后将其工厂放入三级缓存
    • 准备注入 A 时,发现 A 已在三级缓存中(虽未完成初始化)。
    • 通过 A 的工厂从三级缓存获取 A 的早期引用,注入到 B 中。
    • B 完成初始化,放入一级缓存
  3. 继续创建 A

    • 从一级缓存获取已完成初始化的 B,注入到 A 中。
    • A 完成初始化,放入一级缓存

4. 关键代码逻辑

AbstractBeanFactory.doGetBean()方法中,获取 Bean 时会按以下顺序检查缓存:

java

kotlin 复制代码
// 简化版逻辑
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 一级缓存:检查是否存在完全初始化的Bean
    Object singletonObject = this.singletonObjects.get(beanName);
    
    // 若Bean正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 二级缓存:检查是否存在早期Bean引用
        singletonObject = this.earlySingletonObjects.get(beanName);
        
        // 三级缓存:允许提前引用时,通过工厂获取早期Bean
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 从三级缓存获取工厂
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 通过工厂生成早期Bean
                            singletonObject = singletonFactory.getObject();
                            // 将早期Bean放入二级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 从三级缓存移除工厂
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

5. 为什么需要三级缓存?二级不够吗?

三级缓存的核心目的是支持 AOP 代理

  • 若仅用二级缓存,早期暴露的 Bean 无法被代理(因代理通常在初始化后生成)。
  • 通过三级缓存的ObjectFactory回调,Spring 可在获取早期引用时动态生成代理对象(若需要),确保注入的是代理实例而非原始 Bean。

6. 局限性

  • 仅支持单例 Bean:原型(prototype)作用域的 Bean 不支持循环依赖,会抛出异常。
  • 构造器注入不支持:若循环依赖发生在构造器中(如 A 的构造器依赖 B,B 的构造器依赖 A),三级缓存无法解决,需重构代码。

总结

Spring 的三级缓存机制通过提前暴露未完成初始化的 Bean 引用,结合动态代理技术,优雅地解决了单例 Bean 的循环依赖问题。这一设计体现了 Spring 在保证功能完整性和性能之间的精妙平衡。

相关推荐
qq_256247051 小时前
Google 账号防封全攻略:从避坑、保号到申诉解封
后端
MX_93591 小时前
使用Spring的BeanFactoryPostProcessor扩展点完成自定义注解扫描
java·后端·spring
弹简特1 小时前
【JavaEE05-后端部分】使用idea社区版从零开始创建第一个 SpringBoot 程序
java·spring boot·后端
爬山算法2 小时前
Hibernate(81)如何在数据同步中使用Hibernate?
java·后端·hibernate
Ivanqhz2 小时前
现代异构高性能计算(HPC)集群节点架构
开发语言·人工智能·后端·算法·架构·云计算·边缘计算
Loo国昌2 小时前
【大模型应用开发】第三阶段:深度解析检索增强生成(RAG)原理
人工智能·后端·深度学习·自然语言处理·transformer
Demon_Hao2 小时前
Spring Boot开启虚拟线程ScopedValue上下文传递
java·spring boot·后端
小高Baby@2 小时前
ShouldBind、ShouldBindJson、ShouldBindQuery的区别
后端·golang
BYSJMG2 小时前
计算机毕设选题推荐:基于Hadoop的交通事故数据可视化分析系统
大数据·vue.js·hadoop·分布式·后端·信息可视化·课程设计
野犬寒鸦2 小时前
从零起步学习并发编程 || 第三章:JMM(Java内存模型)详解及对比剖析
java·服务器·开发语言·分布式·后端·学习·spring