Spring 三级缓存与循环依赖
一、循环依赖概述
什么是循环依赖
java
// 场景1:构造器循环依赖
@Service
public class A {
public A(B b) { } // A → B
}
@Service
public class B {
public B(A a) { } // B → A
}
// 场景2:Setter 循环依赖
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
// 场景3:自身循环依赖
@Service
public class A {
@Autowired
private A self;
}依赖图示
┌─────────────────────────────────────────────────────────────────┐
│ 循环依赖示意 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 构造器循环依赖(无法解决 ❌) │
│ │
│ ┌─────┐ │
│ │ A │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ B │ │
│ │ │ │ │
│ └──┬──┘ │
│ │ │
│ └──────────────────────────────────────┘ │
│ │
│ Setter 循环依赖(可以解决 ✅) │
│ │
│ ┌─────┐ ┌─────┐ │
│ │ A │────▶│ B │ │
│ │ │◀────│ │ │
│ └─────┘ └─────┘ │
│ │ │ │
│ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘二、三级缓存详解
缓存结构
┌─────────────────────────────────────────────────────────────────┐
│ Spring 三级缓存 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 第一级缓存 (singletonObjects) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Map<String, Object> │ │ │
│ │ │ 完全初始化好的 Bean │ │ │
│ │ │ 可以直接使用 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 第二级缓存 (earlySingletonObjects) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Map<String, Object> │ │ │
│ │ │ 早期暴露的 Bean(未完成属性填充和方法调用) │ │ │
│ │ │ 仅完成实例化 + 属性填充 + init 调用 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 第三级缓存 (singletonFactories) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Map<String, ObjectFactory<?>> │ │ │
│ │ │ Bean 工厂,用于创建早期引用 │ │ │
│ │ │ getObject() 返回早期代理对象 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘源码位置
java
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry extends FactoryBeanRegistrySupport {
// 第一级缓存:完全初始化好的 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 第二级缓存:早期暴露的 Bean(未完成初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 第三级缓存:Bean 工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}三、解决流程
正常 Bean 创建流程
┌─────────────────────────────────────────────────────────────────┐
│ Bean 创建完整流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. createBeanInstance() │
│ └─▶ 调用构造函数创建实例 │
│ └─▶ 放入第三级缓存 singletonFactories │
│ │
│ 2. populateBean() │
│ └─▶ 属性填充(依赖注入) │
│ └─▶ 处理 @Autowired、@Value 等 │
│ │
│ 3. initializeBean() │
│ └─▶ 调用 init 方法 │
│ └─▶ BeanPostProcessor.postProcessAfterInitialization │
│ │
│ 4. 放入第一级缓存 │
│ └─▶ 从二、三级缓存移除 │
│ │
└─────────────────────────────────────────────────────────────────┘循环依赖解决流程
┌─────────────────────────────────────────────────────────────────┐
│ 循环依赖解决详细流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 场景:A → B → A 循环依赖 │
│ │
│ Step 1: 创建 A │
│ ├─ createBeanInstance() → A 实例 │
│ ├─ 放入第三级缓存 singletonFactories │
│ │ └─ A 的 ObjectFactory: () -> getEarlyBeanReference(A) │
│ │ │
│ └─ populateBean() 发现依赖 B │
│ │
│ Step 2: 创建 B │
│ ├─ createBeanInstance() → B 实例 │
│ ├─ 放入第三级缓存 singletonFactories │
│ │ │
│ └─ populateBean() 发现依赖 A │
│ │
│ Step 3: 获取 A 的早期引用 │
│ ├─ 先查第一级缓存 → 没有 │
│ ├─ 再查第二级缓存 → 没有 │
│ ├─ 再查第三级缓存 → 有! │
│ │ └─ 调用 getObject() 获取 A 的早期引用(可能已被代理) │
│ ├─ 放入第二级缓存 earlySingletonObjects │
│ └─ 从第三级缓存移除 │
│ │
│ Step 4: B 完成创建 │
│ ├─ populateBean() 继续 │
│ ├─ initializeBean() │
│ ├─ 放入第一级缓存 │
│ └─ 从二、三级缓存移除 │
│ │
│ Step 5: 回到 A 的创建 │
│ ├─ 获得 B 的完整引用 │
│ ├─ populateBean() 继续 │
│ ├─ initializeBean() │
│ └─ 放入第一级缓存 │
│ │
└─────────────────────────────────────────────────────────────────┘核心代码
java
// getSingleton() 方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 先查第一级缓存
Object singleton = this.singletonObjects.get(beanName);
if (singleton == null && isSingletonCurrentlyInCreation(beanName)) {
// 2. 再查第二级缓存(需要加锁)
synchronized (this.earlySingletonObjects) {
singleton = this.earlySingletonObjects.get(beanName);
if (singleton == null && allowEarlyReference) {
// 3. 再查第三级缓存
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 获取早期引用
singleton = singletonFactory.getObject();
// 放入第二级缓存
this.earlySingletonObjects.put(beanName, singleton);
// 移除第三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singleton;
}四、为什么需要第三级缓存
不用第三级缓存可以吗?
java
// 假设只有两级缓存
// 问题:如何保证返回的是正确的代理对象?第三级缓存的作用
┌─────────────────────────────────────────────────────────────────┐
│ 第三级缓存存在的意义 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 目的:延迟代理对象的创建 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 如果直接创建代理对象: │ │
│ │ - 时机太早(Bean 可能不需要代理) │ │
│ │ - 可能被多次重复创建 │ │
│ │ │ │
│ │ 第三级缓存的作用: │ │
│ │ - 存储 ObjectFactory,延迟获取早期引用 │ │
│ │ - 只有真正需要时(如循环依赖)才调用 │ │
│ │ - 保证每个 Bean 只创建一次早期引用 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 流程: │
│ 1. 放入 ObjectFactory │
│ 2. 需要时调用 getObject() │
│ 3. 获取后放入第二级缓存,移除第三级 │
│ │
└─────────────────────────────────────────────────────────────────┘完整流程图
┌─────────────────┐
│ 创建 Bean A │
└────────┬────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 第一级 │ │ 第二级 │ │ 第三级 │
│ singleton │ │ earlySingle │ │ singleFact │
│ Objects │ │ Objects │ │ -ories │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
│ │ │
A创建完成 ◀───┼──────────────────┼──────────────────┼
│ │ │
│ │ │
populate(A) ◀─┼──────────────────┼──────────────────┼
发现依赖B │ │ │
│ │ │
┌─────────┴─────────┐ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ │ │ │ │ │ │ │
│ 没有 │ │ 没有 │ │ 没有 │ │ 有 │
│ │ │ │ │ │ │Object │
│ │ │ │ │ │ │Factory │
└────────┘ └────────┘ └────────┘ └────┬───┘
│
getEarlyBeanReference()
│
调用ObjectFactory.getObject()
│
▼
┌─────────────────────┐
│ 获取A的早期引用 │
│ (可能返回代理对象) │
└──────────┬──────────┘
│
放入二级缓存,移除三级缓存
│
▼
┌─────────────────────┐
│ A早期引用 │
└──────────┬──────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ 第一级 │ │ 第二级 │ │ 第三级 │
│ 有 │ │ 有 │ │ 无 │
└────────┘ └────────┘ └────────┘五、构造器循环依赖为什么不能解决
┌─────────────────────────────────────────────────────────────────┐
│ 构造器循环依赖无法解决 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 原因:构造器执行时机在 populateBean 之前 │
│ │
│ A 构造器需要 B → 需要先创建 B │
│ B 构造器需要 A → 需要先创建 A │
│ ...无限循环 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ createBeanInstance() → 构造器 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ populateBean() → 属性填充 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ initializeBean() → 初始化 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 构造器阶段无法获取早期引用,因为 Bean 还未创建完成 │
│ │
│ 解决方案: │
│ 1. 改用 Setter 注入 │
│ 2. 使用 @Lazy 延迟加载 │
│ 3. 使用 Provider 延迟引用 │
│ │
└─────────────────────────────────────────────────────────────────┘@Lazy 解决
java
@Service
public class A {
public A(@Lazy B b) { } // 延迟注入
}六、prototype 循环依赖
┌─────────────────────────────────────────────────────────────────┐
│ prototype 循环依赖无法解决 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 原因:prototype Bean 不缓存,每次获取都创建新实例 │
│ │
│ Spring 只缓存 singleton 的 Bean │
│ prototype Bean 每次都走完整创建流程 │
│ 无法通过缓存获取早期引用 │
│ │
│ 解决方案: │
│ 1. 改用 singleton │
│ 2. 使用 ObjectFactory / Provider │
│ │
└─────────────────────────────────────────────────────────────────┘七、@Async 循环依赖问题
┌─────────────────────────────────────────────────────────────────┐
│ @Async 循环依赖问题 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 问题:@Async 会生成代理对象,但三级缓存存的是 ObjectFactory │
│ │
│ @Async 代理创建时机: │
│ - AsyncAutoConfiguration │
│ - BeanPostProcessor.postProcessAfterInitialization │
│ │
│ 如果在 populateBean 时发现循环依赖,调用 ObjectFactory.getObject()
│ 此时 @Async 代理可能还未创建 │
│ │
│ 解决方案: │
│ spring.main.allow-circular-references=false │
│ 或升级 Spring 版本 │
│ │
└─────────────────────────────────────────────────────────────────┘八、面试高频问题
Q1: 什么是循环依赖?
两个或多个 Bean 相互依赖,形成闭环。如 A→B→A
Q2: Spring 三级缓存作用?
- 一级:完全初始化好的 Bean
- 二级:早期暴露的 Bean(未完成初始化)
- 三级:Bean 工厂,延迟创建早期引用
Q3: 哪些循环依赖无法解决?
- 构造器循环依赖
- prototype Bean 的循环依赖
- @Async 代理的循环依赖
Q4: 为什么需要第三级缓存?
延迟代理对象的创建,保证在真正需要时才创建早期引用
Q5: 如何解决循环依赖?
- 改用 setter 注入
- 使用 @Lazy 延迟加载
- 使用 @Autowired(required = false) 可选依赖
九、下一章预告
下一章我们将学习 Spring 总结与实战:
- 全家桶核心概念回顾
- 企业级应用架构设计
- 常用配置汇总