Skip to content

Spring 如何解决循环依赖(三级缓存)

约 866 字大约 3 分钟

Spring框架小红书美团

2025-04-24

⭐ 题目日期:

美团 - 2025/04/12,小红书 - 2024/11/11

📝 题解:

Spring 通过 三级缓存 机制解决单例 Bean 的循环依赖问题。以下是其核心原理和流程:


1. 三级缓存定义

img


2. 循环依赖处理流程(以 A → B → A 为例)

(1) 创建 Bean A

  1. 实例化 A:调用构造函数创建 A 的原始对象。
  2. 暴露工厂对象:将 A 的工厂对象(ObjectFactory)存入 singletonFactories
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean));

(2) 注入 A 的依赖 B

  1. 发现 A 依赖 B:开始注入 B,但 B 尚未创建。
  2. 创建 Bean B:重复步骤 1,实例化 B 并暴露其工厂对象到 singletonFactories

(3) 注入 B 的依赖 A

  1. 发现 B 依赖 A:尝试从缓存获取 A。
    1. 一级缓存(singletonObjects):无。
    2. 二级缓存(earlySingletonObjects)**:无。
    3. 三级缓存(singletonFactories)**:找到 A 的工厂对象,生成早期 A 对象(可能被 AOP 代理),存入 earlySingletonObjects,并移出三级缓存。
  2. 将早期 A 注入 B:B 完成属性注入和初始化,存入一级缓存 singletonObjects

(4) 完成 A 的初始化

  1. 一级缓存获取 B:将已初始化的 B 注入 A。
  2. 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 兼容:确保代理对象在循环依赖中正确注入。

理解三级缓存机制有助于优化代码设计,规避潜在循环依赖风险。