文章目录

Java Spring循环依赖的三级缓存解决方案原理

发布于 2026-04-30 01:26:07 · 浏览 2 次 · 评论 0 条

Java Spring循环依赖的三级缓存解决方案原理

Spring 框架通过其 IoC 容器管理 Bean 的生命周期,但在面对两个或多个 Bean 互相引用(例如 A 依赖 B,B 依赖 A)的“循环依赖”场景时,直接的实例化会导致无限递归,最终抛出 StackOverflowError。为了解决这一问题,Spring 设计了一套精妙的三级缓存机制。这套机制利用不同状态的缓存,在不改变 Bean 核心创建流程的前提下,打破了循环引用的死锁。


一、 理解三级缓存的结构

Spring 使用三个 Map 结构来存储不同状态下的 Bean 对象。这三级缓存各自承担不同的职责,确保对象在创建的各个阶段都能被正确引用。

级别 变量名 中文名称 存放内容 用途
一级缓存 singletonObjects 单例对象池 完全初始化好的 Bean 存放已经完成实例化、属性填充和初始化的成品 Bean,可以直接供外部使用。
二级缓存 earlySingletonObjects 早期对象缓存 仅实例化(未填充属性)的 Bean 存放提前暴露的 Bean(半成品),用于解决循环依赖中的引用问题。
三级缓存 singletonFactories 单例工厂缓存 创建 Bean 的 ObjectFactory(工厂对象) 存放可以生成 Bean 的工厂(通常是 Lambda 表达式),主要用于在循环依赖发生时生成代理对象(AOP)。

二、 三级缓存解决循环依赖的流程

假设场景:Bean A 依赖于 Bean B,同时 Bean B 也依赖于 Bean A。

1. 开始创建 Bean A

Spring 容器调用 getBean(A) 开始创建 A。此时容器检查一级缓存,发现不存在。Spring 执行 A 的构造函数,完成对象的实例化(此时 A 只是一个空壳,属性 B 尚未注入)。紧接着,Spring 一个可以生成 A 的 ObjectFactory 放入三级缓存 singletonFactories 中。

2. 填充 Bean A 的属性

Spring 开始填充 A 的属性,发现需要依赖 Bean B,于是调用 getBean(B)

3. 开始创建 Bean B

容器检查一级缓存,B 不存在。Spring 执行 B 的构造函数完成实例化,同样将 B 的 ObjectFactory 放入三级缓存。

4. 填充 Bean B 的属性

Spring 开始填充 B 的属性,发现需要依赖 Bean A,于是再次调用 getBean(A)

5. 查找 Bean A 的缓存

这是解决循环依赖的关键步骤。容器再次查找 Bean A:

  1. 检查一级缓存 singletonObjects:A 尚未完成创建,不存在。
  2. 检查二级缓存 earlySingletonObjects:不存在。
  3. 检查三级缓存 singletonFactories找到了 A 的工厂对象。

6. 生成早期引用与缓存升级

容器调用三级缓存中 A 的工厂对象的 getObject() 方法。这一步非常关键,它会根据 A 是否配置了 AOP(面向切面编程)来决定返回原始对象还是代理对象。

  • 获取到对象后,Spring 将其放入二级缓存 earlySingletonObjects 中。
  • 同时,从三级缓存中移除 A 的工厂对象。

此时,Bean B 获得了 Bean A 的早期引用(可能是代理对象)。

7. 完成 Bean B 的初始化

Bean B 拿到了 A 的引用,完成属性填充和初始化。Bean B 此时是一个完整的 Bean,被放入一级缓存 singletonObjects 中,并从二、三级缓存中清理(如果存在)。

8. 回溯完成 Bean A 的初始化

Bean B 创建完成并返回给 Bean A。Bean A 获得 B 的引用,完成自己的属性填充和初始化。最终,Bean A 也变成了一个完整的 Bean,被放入一级缓存 singletonObjects 中,并从二级缓存中移除。


三、 核心流程可视化

以下流程图展示了 A、B 互相依赖时,各级缓存的变化与对象流转过程:

graph TD subgraph "创建 Bean A" A1[实例化 A] --> A2[将 A 工厂放入三级缓存] A2 --> A3[填充属性: 发现需要 B] end subgraph "创建 Bean B" A3 --> B1[实例化 B] B1 --> B2[将 B 工厂放入三级缓存] B2 --> B3[填充属性: 发现需要 A] end subgraph "获取 A 的早期引用" B3 --> C1{检查一级缓存 A} C1 -- 不存在 --> C2{检查二级缓存 A} C2 -- 不存在 --> C3{检查三级缓存 A} C3 -- 命中 --> C4[调用工厂生成 A 实例/代理] C4 --> C5[存入二级缓存并移除三级] C5 --> B4[B 注入 A 引用] end subgraph "完成创建" B4 --> B5[B 初始化完成并存入一级缓存] B5 --> A4[A 注入 B 实例] A4 --> A5[A 初始化完成并存入一级缓存] end

四、 为什么必须使用三级缓存?

很多开发者会疑惑:两级缓存似乎足以解决循环引用(一级存成品,二级存半成品),为什么还需要第三级?

  1. 延迟代理对象的创建(核心原因)
    Spring 的设计原则是:Bean 的代理对象(AOP)通常应该在初始化阶段最后一步创建。如果只有两级缓存,Spring 必须在实例化后立即判断是否需要代理,并提前创建代理对象放入二级缓存。这会迫使所有 Bean 的代理逻辑提前执行,无论是否发生循环依赖。
    而使用三级缓存,存放的是工厂而不是具体的对象。只有当真正发生循环依赖(B 需要引用 A)时,才会调用工厂生成对象。如果 A 不被其他 Bean 循环引用,这个工厂永远不会被调用,A 的代理对象就可以按照正常流程在最后一步创建。这保证了 Spring 处理流程的优雅性和性能。

  2. 保持结构的一致性
    三级缓存结构清晰地划分了“工厂”、“早期引用”和“成品”三个阶段,使得代码逻辑分离,易于扩展和维护。


五、 极简代码示例

为了更直观地理解,以下是模拟 Spring 核心逻辑的伪代码:

// 1. 容器获取 Bean A
Object beanA = getBean("A");

public Object getBean(String beanName) {
    // 2. 尝试从一级缓存获取成品
    Object singleton = getSingleton(beanName);
    if (singleton != null) {
        return singleton;
    }

    // 3. 如果未找到,创建 Bean 实例(实例化,非初始化)
    Object beanInstance = createBeanInstance(beanName);

    // 4. 将工厂放入三级缓存 (关键步骤)
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanInstance));

    // 5. 填充属性(触发循环依赖 B -> A)
    populateBean(beanName, beanInstance);

    // 6. 初始化 Bean
    initializeBean(beanName, beanInstance);

    // 7. 加入一级缓存
    addSingleton(beanName, beanInstance);
    return beanInstance;
}

protected Object getSingleton(String beanName) {
    // 查一级
    Object singletonObject = singletonObjects.get(beanName);
    if (singletonObject == null) {
        // 查二级
        singletonObject = earlySingletonObjects.get(beanName);
        if (singletonObject == null) {
            // 查三级
            ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
            if (singletonFactory != null) {
                // 调用工厂获取对象(可能是代理)
                singletonObject = singletonFactory.getObject();
                // 升入二级,删三级
                earlySingletonObjects.put(beanName, singletonObject);
                singletonFactories.remove(beanName);
            }
        }
    }
    return singletonObject;
}

通过这套机制,Spring 巧妙地利用时间差和状态分离,在不牺牲 Bean 生命周期完整性的前提下,完美解决了单例 Bean 的循环依赖问题。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文