一、3 级缓存的源码定义

// DefaultSingletonBeanRegistry(Spring 核心源码)
public class DefaultSingletonBeanRegistry {
    
    /** 1 级缓存:存放完全初始化好的 Bean(成品) */
    private final Map<String, Object> singletonObjects = 
            new ConcurrentHashMap<>(256);
    
    /** 2 级缓存:存放早期暴露的 Bean(半成品,已实例化但未填充属性) */
    private final Map<String, Object> earlySingletonObjects = 
            new ConcurrentHashMap<>(16);
    
    /** 3 级缓存:存放 ObjectFactory(能产生早期 Bean 引用) */
    private final Map<String, ObjectFactory<?>> singletonFactories = 
            new HashMap<>(16);
}

3 级缓存本质:3 个 Map,分别存不同状态的 Bean。

缓存 内容 状态
singletonObjects(一级) 完整 Bean 已实例化 + 已注入 + 已初始化
earlySingletonObjects(二级) 早期 Bean 已实例化 + 未注入 + 未初始化
singletonFactories(三级) ObjectFactory 工厂方法(产生早期 Bean)

二、3 级缓存解决循环依赖的完整流程

场景:A 和 B 互相依赖(字段注入或 setter 注入
@Servicepublic class A {    @Autowired    private B b;  // A 依赖 B}@Servicepublic class B {    @Autowired    private A a;  // B 依赖 A}
2.1 完整流程┌─────────────────────────────────────────────────────────┐
│ 步骤 1:实例化 A                                           │
│   new A() → 创建 A 原始对象(属性 b 还是 null)                 │
└─────────────────────────────────────────────────────────┘
        ↓
┌─────────────────────────────────────────────────────────┐
│ 步骤 2:暴露早期引用(核心!)                              │
│   A-ObjectFactory → singletonFactories(3 级缓存)         │
│   ⚠️ 此时 A 还是半成品,b=null                              │
└─────────────────────────────────────────────────────────┘
        ↓
┌─────────────────────────────────────────────────────────┐
│ 步骤 3:给 A 注入依赖                                       │
│   需要注入 B(@Autowired B b)                              │
│   ↓                                                       │
│   去容器找 B → B 不存在 → 开始创建 B                        │
└─────────────────────────────────────────────────────────┘
        ↓
┌─────────────────────────────────────────────────────────┐
│ 步骤 4:实例化 B                                           │
│   new B() → 创建 B 原始对象(属性 a 还是 null)                 │
└─────────────────────────────────────────────────────────┘
        ↓
┌─────────────────────────────────────────────────────────┐
│ 步骤 5:从 3 级缓存获取 A(关键步骤!)                       │
│   B 需要注入 A → 容器找 A → A 在 singletonFactories 中        │
│   ↓                                                       │
│   调用 A-ObjectFactory.getObject() → 创建 A 早期引用(半成品)│
│   ↓                                                       │
│   ⚠️ A 早期引用 → earlySingletonObjects(2 级缓存)         │
│   此时 A 还是半成品,b=null                                │
└─────────────────────────────────────────────────────────┘
        ↓
┌─────────────────────────────────────────────────────────┐
│ 步骤 6:将 A 注入给 B                                      │
│   B.a = A 早期引用(半成品)                                │
│   ↓                                                       │
│   B 创建成功(B.a 已注入)                                  │
│   ↓                                                       │
│   B → singletonObjects(1 级缓存,完整 Bean)                │
└─────────────────────────────────────────────────────────┘
        ↓
┌─────────────────────────────────────────────────────────┐
│ 步骤 7:回到 A,注入 B                                      │
│   步骤 3 暂停的地方继续:把 B 注入给 A                       │
│   A.b = B 完整引用                                          │
│   ↓                                                       │
│   A 创建成功(A.b 已注入)                                  │
│   ↓                                                       │
│   A → singletonObjects(1 级缓存,完整 Bean)                │
└─────────────────────────────────────────────────────────┘
2.2 关键时序

步骤 1: 实例化 A → 原始对象 A
步骤 2: A-ObjectFactory → singletonFactories(3 级)
步骤 3: 需要注入 B
步骤 4: 实例化 B → 原始对象 B
步骤 5: 从 singletonFactories 获取 A-ObjectFactory
        → 创建 A 早期引用
        → 放入 earlySingletonObjects(2 级)
步骤 6: B.a = A 早期引用 → B 创建成功 → singletonObjects(1 级)
步骤 7: A.b = B 完整引用 → A 创建成功 → singletonObjects(1 级)

三、3 级缓存的 4 大核心要点

3.1 为什么需要 3 级缓存?1 级不行吗?

1 级缓存为什么不行:// 假设只有 1 级缓存
步骤 1: new A() → A 原始对象
步骤 2: 需要注入 B → B 不存在 → 创建 B
步骤 3: B 需要注入 A → A 还是 null(没创建完)→ ❌ 死循环

2 级缓存(早期引用)为什么不够:

// 假设只有 2 级缓存(早期引用)
步骤 1: new A() → A 原始对象
步骤 2: A 早期引用 → earlySingletonObjects(2 级)
步骤 3: 需要注入 B → 创建 B
步骤 4: B 需要注入 A → 从 earlySingletonObjects 取 A 早期引用 ✅
步骤 5: B 创建成功
步骤 6: A 注入 B 成功

2 级缓存理论上能解决! 但 Spring 还是要 3 级,原因是 AOP

3.2 3 级缓存 vs AOP(核心原因

@Service
public class A {
    @Autowired
    private B b;
    
    @Transactional  // ⚠️ A 需要 AOP 代理
    public void doSomething() { ... }
}        

问题: A 在步骤 1 实例化时还是原始对象(没 AOP 代理),但 B 在步骤 4 需要注入 A,如果注入原始对象,后续 A 的 AOP 代理就废了

3 级缓存的解决:

// 3 级缓存存的不是原始对象,是 ObjectFactory
singletonFactories.put("A", () -> {
    // 这个 lambda 在调用时才执行
    // 此时可以决定返回原始对象还是 AOP 代理对象
    return getEarlyBeanReference("A", mbd, A 原始对象);
});

// getEarlyBeanReference 会判断 A 是否需要 AOP 代理
// 需要:返回 AOP 代理对象
// 不需要:返回原始对象

关键事实:

  • 3 级缓存存的是 ObjectFactory(lambda / 工厂方法)
  • B 注入 A 时调用 ObjectFactory.getObject()
  • getObject() 内部判断是否需要 AOP 代理
  • 这样保证 B 拿到的 A 是"最终版本"(可能是代理)
3.3 完整时序图(含 AOP)

步骤 1: 实例化 A → 原始对象 A
步骤 2: A-ObjectFactory → singletonFactories(3 级)
        ObjectFactory 内部:  getEarlyBeanReference() → 返回 A 代理对象(@Transactional 触发 AOP)
步骤 3: 需要注入 B
步骤 4: 实例化 B → 原始对象 B
步骤 5: 从 singletonFactories 获取 A-ObjectFactory
        → getObject() → 返回 A 代理对象(AOP 已生成)
        → 放入 earlySingletonObjects(2 级)
步骤 6: B.a = A 代理对象 → B 创建成功
步骤 7: A.b = B 完整引用 → A 创建成功
        ⚠️ A 此时也会经过 BeanPostProcessor#after 生成 AOP 代理
        ⚠️ 但 A 早期引用已经是代理对象了,所以最终一致

3.4 为什么 3 级缓存能解决,构造注入不行?

关键差异:

注入方式 实例化 vs 注入 3 级缓存能否解决
字段注入 实例化(new A)→ 暴露 ObjectFactory → 注入(setter 注入 b) ✅ 能解决(因为 setter 注入是后置的)
setter 注入 同上 ✅ 能解决
构造注入 实例化(new A(B b))→ 必须立刻拿到 b ❌ 不能解决(构造方法执行时 b 还不存在)

结论:

  • 字段/setter 注入:实例化(先 new 一个空对象)→ 暴露 ObjectFactory → 注入依赖
  • 构造注入:实例化(必须立刻拿到所有依赖)→ 没法分两步

四、面试官追问应对

追问:Spring 怎么解决循环依赖?

"3 级缓存

1.singletonObjects(完整 Bean)

2.earlySingletonObjects(早期引用)

3.singletonFactories(ObjectFactory)

    核心流程:

    1.A 实例化 → 暴露 ObjectFactory(3 级)

    2.A 注入 B → 创建 B

    3.B 注入 A → 从 3 级缓存获取 A 早期引用 → B 创建成功

    4.A 注入 B → A 创建成功

      注意:构造注入的循环依赖 3 级缓存解不了,要用 @Lazy 或改成 setter/字段注入。"

      追问 2:3 级缓存为什么能解决 AOP + 循环依赖?

      "3 级缓存存的不是 Bean 本身,是 ObjectFactory(lambda)。

      ObjectFactory.getObject() 内部会判断是否需要 AOP 代理

      • 需要:返回 AOP 代理对象
      • 不需要:返回原始对象

      这样 B 注入的 A 始终是'最终版本'(可能是代理),保证 AOP 不会失效。"

      追问 3:构造注入的循环依赖为什么 3 级缓存解不了?

      "构造注入必须立刻拿到所有依赖不能分两步(实例化 → 注入)。

      但 3 级缓存的核心是**'先实例化空对象,再注入依赖',这要求注入是后置的**。

      • 字段注入 ✅(后置,setter 注入)
      • setter 注入 ✅(后置)
      • 构造注入 ❌(前置,必须立刻拿到依赖)

      解决:构造注入 + @Lazy(参数是代理对象,不真正创建)。"

      追问 4:@Lazy 怎么解决循环依赖?

      "@Lazy 注入的是代理对象(不是 Bean 本体),第一次调用时才真正创建。

      流程:

      1.A 构造注入 @Lazy B → 拿到 B 的代理对象

      2.A 创建成功

      3.真正用到 B 时才创建 B

      4.B 创建时 A 已经存在 → 不会循环

        @Lazy 是打破时序的关键。"

        追问 5:3 级缓存能解决构造注入 + @Async 的循环依赖吗?

        "不能

        • 字段/setter 注入 + @Async ✅(3 级缓存 + AOP 代理都能解决)
        • 构造注入 + @Async ❌(构造注入本身不能循环)

        解决:构造注入 + @Lazy + @Async(两个都用)。"

        五、记忆口诀

        "3 级缓存:完整 → 早期 → ObjectFactory"

        "ObjectFactory 内部判断 AOP,返回代理或原始对象"

        "字段/setter 注入能解,构造注入不能解"

        "构造 + @Lazy 解决循环,时序是关键"

        "prototype 循环依赖 3 级缓存解不了"

        六、一句话总结

        "Spring 3 级缓存解决循环依赖

        1.singletonObjects(完整 Bean,1 级)

        2.earlySingletonObjects(早期引用,2 级)

        3.singletonFactories(ObjectFactory,3 级)

          核心:实例化 A → 暴露 ObjectFactory → 注入 B → B 从 ObjectFactory 拿 A 早期引用 → B 创建成功 → A 注入 B → A 创建成功。

          关键限制:构造注入的循环依赖 3 级缓存解不了,要用 @Lazy 或改成 setter/字段注入。"

          Logo

          汇聚全球AI编程工具,助力开发者即刻编程。

          更多推荐