外观
Spring 如何解决循环依赖(三级缓存)
⭐ 题目日期:
美团 - 2025/04/12,小红书 - 2024/11/11
📝 题解:
Spring 通过 三级缓存 机制解决单例 Bean 的循环依赖问题。以下是其核心原理和流程:
1. 三级缓存定义
2. 循环依赖处理流程(以 A → B → A 为例)
(1) 创建 Bean A
- 实例化 A:调用构造函数创建 A 的原始对象。
- 暴露工厂对象:将 A 的工厂对象(
ObjectFactory
)存入singletonFactories
。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean));
(2) 注入 A 的依赖 B
- 发现 A 依赖 B:开始注入 B,但 B 尚未创建。
- 创建 Bean B:重复步骤 1,实例化 B 并暴露其工厂对象到
singletonFactories
。
(3) 注入 B 的依赖 A
- 发现 B 依赖 A:尝试从缓存获取 A。
- 一级缓存(singletonObjects):无。
- 二级缓存(earlySingletonObjects)**:无。
- 三级缓存(singletonFactories)**:找到 A 的工厂对象,生成早期 A 对象(可能被 AOP 代理),存入
earlySingletonObjects
,并移出三级缓存。
- 将早期 A 注入 B:B 完成属性注入和初始化,存入一级缓存
singletonObjects
。
(4) 完成 A 的初始化
- 从一级缓存获取 B:将已初始化的 B 注入 A。
- A 完成初始化:A 存入一级缓存,移出二级缓存。
3. 为何需要三级缓存?
- 一级缓存(singletonObjects):存放最终可用的 Bean,避免重复创建。
- 二级缓存(earlySingletonObjects):暂存早期对象,防止重复调用工厂方法。
- 三级缓存(singletonFactories):
- 延迟生成代理对象:若 Bean 需 AOP 代理,工厂方法可返回代理对象,确保依赖注入的是代理后的 Bean。
- 分离实例化与初始化:解决循环依赖时,允许提前暴露非完整 Bean。
若只有两级缓存:无法处理 AOP 代理对象的循环依赖,工厂需在实例化时立即生成代理对象,可能破坏 Bean 生命周期。
4. 解决循环依赖的限制
- 仅支持单例Bean:原型(Prototype)作用域的 Bean 无法通过缓存解决循环依赖。
- 构造函数循环依赖无法解决:若循环依赖通过构造函数注入(而非 Setter/字段注入),Spring 会直接抛出
BeanCurrentlyInCreationException
。
// 构造函数循环依赖示例(无法解决)
@Component
public class A {
private final B b;
public A(B b) { this.b = b; } // 构造函数注入
}
@Component
public class B {
private final A a;
public B(A a) { this.a = a; } // 构造函数注入
}
5. 如何避免循环依赖?
- 设计优化:重构代码,解耦相互依赖(如引入中间层、使用事件驱动)。
- 延迟注入:使用
@Lazy
注解,延迟加载依赖。
@Component
public class A {
@Autowired
@Lazy // 延迟注入 B
private B b;
}
- Setter/字段注入替代构造函数注入:优先使用非构造函数注入方式。
6. 总结
Spring 通过三级缓存机制 提前暴露未初始化完成的 Bean,结合动态代理技术,解决了单例 Bean 的循环依赖问题。其核心价值在于:
- 性能优化:避免重复创建对象。
- 生命周期管理:分离实例化与初始化,支持复杂依赖场景。
- AOP 兼容:确保代理对象在循环依赖中正确注入。
理解三级缓存机制有助于优化代码设计,规避潜在循环依赖风险。