Spring 3 级缓存解决循环依赖 一、3 级缓存的源码定义// DefaultSingletonBeanRegistrySpring 核心源码public class DefaultSingletonBeanRegistry {/** 1 级缓存存放完全初始化好的 Bean成品 */private final MapString, Object singletonObjects new ConcurrentHashMap(256);/** 2 级缓存存放早期暴露的 Bean半成品已实例化但未填充属性 */private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16);/** 3 级缓存存放 ObjectFactory能产生早期 Bean 引用 */private final MapString, 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 → singletonFactories3 级缓存 ││ ⚠️ 此时 A 还是半成品bnull │└─────────────────────────────────────────────────────────┘↓┌─────────────────────────────────────────────────────────┐│ 步骤 3给 A 注入依赖 ││ 需要注入 BAutowired 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 早期引用 → earlySingletonObjects2 级缓存 ││ 此时 A 还是半成品bnull │└─────────────────────────────────────────────────────────┘↓┌─────────────────────────────────────────────────────────┐│ 步骤 6将 A 注入给 B ││ B.a A 早期引用半成品 ││ ↓ ││ B 创建成功B.a 已注入 ││ ↓ ││ B → singletonObjects1 级缓存完整 Bean │└─────────────────────────────────────────────────────────┘↓┌─────────────────────────────────────────────────────────┐│ 步骤 7回到 A注入 B ││ 步骤 3 暂停的地方继续把 B 注入给 A ││ A.b B 完整引用 ││ ↓ ││ A 创建成功A.b 已注入 ││ ↓ ││ A → singletonObjects1 级缓存完整 Bean │└─────────────────────────────────────────────────────────┘2.2 关键时序步骤 1: 实例化 A → 原始对象 A步骤 2: A-ObjectFactory → singletonFactories3 级步骤 3: 需要注入 B步骤 4: 实例化 B → 原始对象 B步骤 5: 从 singletonFactories 获取 A-ObjectFactory→ 创建 A 早期引用→ 放入 earlySingletonObjects2 级步骤 6: B.a A 早期引用 → B 创建成功 → singletonObjects1 级步骤 7: A.b B 完整引用 → A 创建成功 → singletonObjects1 级三、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 早期引用 → earlySingletonObjects2 级步骤 3: 需要注入 B → 创建 B步骤 4: B 需要注入 A → 从 earlySingletonObjects 取 A 早期引用 ✅步骤 5: B 创建成功步骤 6: A 注入 B 成功2 级缓存理论上能解决但 Spring 还是要 3 级原因是 AOP。3.2 3 级缓存 vs AOP核心原因Servicepublic class A {Autowiredprivate B b;Transactional // ⚠️ A 需要 AOP 代理public void doSomething() { ... }}问题A 在步骤 1 实例化时还是原始对象没 AOP 代理但 B 在步骤 4 需要注入 A如果注入原始对象后续 A 的 AOP 代理就废了。3 级缓存的解决// 3 级缓存存的不是原始对象是 ObjectFactorysingletonFactories.put(A, () - {// 这个 lambda 在调用时才执行// 此时可以决定返回原始对象还是 AOP 代理对象return getEarlyBeanReference(A, mbd, A 原始对象);});// getEarlyBeanReference 会判断 A 是否需要 AOP 代理// 需要返回 AOP 代理对象// 不需要返回原始对象关键事实3 级缓存存的是 ObjectFactorylambda / 工厂方法B 注入 A 时调用 ObjectFactory.getObject()getObject() 内部判断是否需要 AOP 代理这样保证 B 拿到的 A 是最终版本可能是代理3.3 完整时序图含 AOP步骤 1: 实例化 A → 原始对象 A步骤 2: A-ObjectFactory → singletonFactories3 级ObjectFactory 内部: getEarlyBeanReference() → 返回 A 代理对象Transactional 触发 AOP步骤 3: 需要注入 B步骤 4: 实例化 B → 原始对象 B步骤 5: 从 singletonFactories 获取 A-ObjectFactory→ getObject() → 返回 A 代理对象AOP 已生成→ 放入 earlySingletonObjects2 级步骤 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完整 Bean2.earlySingletonObjects早期引用3.singletonFactoriesObjectFactory核心流程1.A 实例化 → 暴露 ObjectFactory3 级2.A 注入 B → 创建 B3.B 注入 A → 从 3 级缓存获取 A 早期引用 → B 创建成功4.A 注入 B → A 创建成功注意构造注入的循环依赖 3 级缓存解不了要用 Lazy 或改成 setter/字段注入。追问 23 级缓存为什么能解决 AOP 循环依赖3 级缓存存的不是 Bean 本身是 ObjectFactorylambda。ObjectFactory.getObject() 内部会判断是否需要 AOP 代理需要返回 AOP 代理对象不需要返回原始对象这样 B 注入的 A 始终是最终版本可能是代理保证 AOP 不会失效。追问 3构造注入的循环依赖为什么 3 级缓存解不了构造注入必须立刻拿到所有依赖不能分两步实例化 → 注入。但 3 级缓存的核心是**先实例化空对象再注入依赖这要求注入是后置的**。字段注入✅后置setter 注入setter 注入✅后置构造注入❌前置必须立刻拿到依赖解决构造注入 Lazy参数是代理对象不真正创建。追问 4Lazy 怎么解决循环依赖Lazy 注入的是代理对象不是 Bean 本体第一次调用时才真正创建。流程1.A 构造注入 Lazy B → 拿到 B 的代理对象2.A 创建成功3.真正用到 B 时才创建 B4.B 创建时 A 已经存在 → 不会循环Lazy 是打破时序的关键。追问 53 级缓存能解决构造注入 Async 的循环依赖吗不能。字段/setter 注入 Async✅3 级缓存 AOP 代理都能解决构造注入 Async❌构造注入本身不能循环解决构造注入 Lazy Async两个都用。五、记忆口诀3 级缓存完整 → 早期 → ObjectFactoryObjectFactory 内部判断 AOP返回代理或原始对象字段/setter 注入能解构造注入不能解构造 Lazy 解决循环时序是关键prototype 循环依赖 3 级缓存解不了六、一句话总结Spring 3 级缓存解决循环依赖1.singletonObjects完整 Bean1 级2.earlySingletonObjects早期引用2 级3.singletonFactoriesObjectFactory3 级核心实例化 A → 暴露 ObjectFactory → 注入 B → B 从 ObjectFactory 拿 A 早期引用 → B 创建成功 → A 注入 B → A 创建成功。关键限制构造注入的循环依赖 3 级缓存解不了要用 Lazy 或改成 setter/字段注入。