Spring 是如何解决循环依赖问题的?

发布于:2025-02-19 ⋅ 阅读:(36) ⋅ 点赞:(0)

Spring 通过三级缓存机制解决单例 Bean 的循环依赖问题,核心思想是提前暴露未完全初始化的 Bean 引用。以下是详细原理和流程:


一、循环依赖的场景

假设存在两个 Bean:

java

复制

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

Spring 需要创建 A 和 B,但两者互相依赖,形成循环依赖。


二、三级缓存的作用

Spring 用三个 Map(缓存)管理 Bean 的创建过程:

  1. 一级缓存 singletonObjects
    存储完全初始化的单例 Bean,可直接使用。

  2. 二级缓存 earlySingletonObjects
    存储未完全初始化的 Bean(已实例化但未填充属性)。

  3. 三级缓存 singletonFactories
    存储 Bean 的 ObjectFactory,用于生成早期 Bean 的引用。


三、解决流程(以 A 和 B 为例)

以下是关键步骤说明:

步骤 1:创建 Bean A
  1. 调用 getBean("A"),检查一级缓存 singletonObjects,未找到。

  2. 实例化 A(调用构造函数),但此时 A 尚未填充属性 b

  3. 将 A 的 ObjectFactory 放入三级缓存 singletonFactories

步骤 2:填充 Bean A 的属性
  1. 尝试为 A 注入属性 b,调用 getBean("B")

  2. 检查一级缓存 singletonObjects,未找到 B。

步骤 3:创建 Bean B
  1. 实例化 B(调用构造函数),此时 B 尚未填充属性 a

  2. 将 B 的 ObjectFactory 放入三级缓存 singletonFactories

步骤 4:填充 Bean B 的属性
  1. 尝试为 B 注入属性 a,调用 getBean("A")

  2. 检查一级缓存 singletonObjects,未找到 A。

  3. 检查三级缓存 singletonFactories,找到 A 的 ObjectFactory,调用其 getObject() 生成早期引用。

  4. 将 A 的早期引用从三级缓存移到二级缓存 earlySingletonObjects

  5. 将 A 的早期引用注入到 B 中,完成 B 的属性填充。

  6. B 初始化完成,放入一级缓存 singletonObjects

步骤 5:完成 Bean A 的初始化
  1. 将 B 的实例注入到 A 中。

  2. A 初始化完成,从二级缓存移除并放入一级缓存 singletonObjects


四、关键机制与限制

1. 三级缓存的必要性
  • 三级缓存 singletonFactories:存储生成早期 Bean 的工厂,解决代理对象(如 AOP)的循环依赖。工厂可以返回原始对象或代理对象。

  • 二级缓存 earlySingletonObjects:避免重复调用 ObjectFactory.getObject(),保证单例唯一性。

2. 支持的范围
  • 仅支持单例 Bean:原型(Prototype)作用域的 Bean 无法解决循环依赖。

  • 仅支持 Setter/Field 注入:构造器注入会直接抛出 BeanCurrentlyInCreationException

3. 构造器注入为何失败?

java

复制

@Component
public class A {
    private final B b;
    public A(B b) { this.b = b; } // 构造器注入
}
  • 在实例化 A 时就需要 B 的实例,但此时 B 尚未创建,无法通过三级缓存解决。


五、总结

Spring 通过三级缓存提前暴露未初始化的 Bean 引用,解决了单例 Bean 的循环依赖问题。核心在于将实例化与初始化分离,允许“未完成”的 Bean 被引用,最终通过依赖注入完成闭环。