一、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/字段注入。"