Spring 的三级缓存与循环依赖详解

发布于:2025-05-27 ⋅ 阅读:(22) ⋅ 点赞:(0)

Spring 的三级缓存与循环依赖详解

🧩 场景示例

@Component
class A {
    @Autowired B b;
}

@Component
class B {
    @Autowired A a;
}

🔁 Spring 如何解决循环依赖?

一、Spring 的三级缓存结构(在 DefaultSingletonBeanRegistry 中)

缓存层级 名称 说明
一级缓存 singletonObjects 完全初始化的 Bean
二级缓存 earlySingletonObjects 已创建但未填充依赖的 early Bean
三级缓存 singletonFactories ObjectFactory 工厂,用于懒加载 Bean

二、调用流程(当 B 注入 A 时)

Spring 调用:

getSingleton("a")

执行顺序:

  1. 查一级缓存 singletonObjects:❌
  2. 查二级缓存 earlySingletonObjects:❌
  3. 查三级缓存 singletonFactories:
    • ✅ 存在 ObjectFactory
    • 调用 getObject() → 得到 early A
    • 放入二级缓存
    • 移除三级缓存记录

三、A 的状态变迁

阶段 A 的位置
正在创建 A 进入三级缓存(工厂)
B 创建时注入 A ObjectFactory.getObject() → early A
early A 进入二级缓存 可用于依赖注入
A 初始化完成 → 一级缓存 进入 singletonObjects

四、三级缓存 vs 二级缓存区别

对比维度 三级缓存 二级缓存
内容 ObjectFactory early Bean 实例(可能未注入完成)
是否立即初始化 ❌ 否,懒加载 ✅ 是,已触发初始化
生命周期 创建阶段 依赖注入阶段
消失时机 调用 getObject() 后立即移除 Bean 初始化完成后移除

五、流程图(简化版)

创建 A → 加入三级缓存(工厂)
      ↓
A 注入 B → 创建 B
      ↓
B 注入 A → getSingleton("a") 查三级缓存
      ↓
执行 ObjectFactory.getObject() → 得到 early A → 存入二级缓存
      ↓
B 初始化完成 → 注入 A → A 完成初始化
      ↓
一级缓存:A 和 B 均完成

✅ 一句话总结

Spring 通过三级缓存机制,允许提前暴露 Bean 引用(early A),打破单例 Bean 的依赖闭环,实现字段/Setter 注入的循环依赖解决。

🧠 深度解析:B 注入 A 的过程中的三级缓存调用与状态变化

❓ 问题 1:怎么调用 ObjectFactory 获取 early A?

当 B 需要注入 A 时,Spring 执行如下逻辑:

/**
 * 获取指定名称的单例 Bean 对象
 * 支持从三级缓存机制中解析 early singleton(用于解决循环依赖)
 */
protected Object getSingleton(String beanName, boolean allowEarlyReference) {

    // 🔵【一级缓存】直接获取已完全初始化的单例对象
    Object singletonObject = singletonObjects.get(beanName);

    // 如果一级缓存没有,且当前 Bean 正在创建中(意味着可能存在循环依赖)
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

        // 🟡【二级缓存】尝试从 earlySingletonObjects 中获取“早期暴露的 Bean 实例”
        singletonObject = earlySingletonObjects.get(beanName);

        // 如果二级缓存也没有,并且允许提前暴露(默认是 true)
        if (singletonObject == null && allowEarlyReference) {

            // 🔴【三级缓存】从 singletonFactories 获取创建早期 Bean 的 ObjectFactory
            ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);

            if (singletonFactory != null) {
                // 🔥 关键调用:通过 ObjectFactory 创建 early singleton(A 的半成品)
                singletonObject = singletonFactory.getObject();

                // 👉 把 early A 放入二级缓存,供依赖注入使用
                earlySingletonObjects.put(beanName, singletonObject);

                // ❌ 从三级缓存中移除 A 的工厂,避免重复创建 early A
                singletonFactories.remove(beanName);
            }
        }
    }

    // 返回最终获取到的 Bean(可能是一级缓存的完整 Bean,也可能是二级缓存中的 early Bean)
    return singletonObject;
}

✔️ singletonFactory.getObject() 触发了 A 的 early reference 实例创建。


❓ 问题 2:A 从三级缓存进入二级缓存,发生了什么变化?

缓存状态 内容
三级缓存 ObjectFactory(尚未创建 A)
二级缓存 early A 对象实例(未注入完)
  • 原本只在三级缓存中存在一个 懒加载工厂
  • 调用 getObject() 后,创建了 early A(半成品对象);
  • 把这个 early A 放进二级缓存,供 B 注入;
  • 同时从三级缓存中将 ObjectFactory 移除,避免重复创建。

❓ 问题 3:三级缓存和二级缓存的核心区别?

属性 三级缓存(singletonFactories) 二级缓存(earlySingletonObjects)
存储内容 ObjectFactory(工厂) early Bean 实例(真实对象)
是否已实例化对象 ❌ 否,懒加载 ✅ 是,已构造未注入完
使用时机 注入前的延迟暴露机制 实际注入引用依赖时
生命周期 调用 getObject() 后立即删除 Bean 初始化完成后删除

✅ 总结

Spring 会在 Bean A 尚未初始化完成时,通过 ObjectFactory 提前暴露其引用,注入给 B 使用,
这个 early A 就从 三级缓存 → 二级缓存,实现了单例 Bean 的“半成品依赖注入”。




网站公告

今日签到

点亮在社区的每一天
去签到