Spring如何解决Bean的循环依赖

Spring 解决单例 Bean 循环依赖的核心机制是 “三级缓存 + 提前暴露未完成初始化的 Bean”,通过在 Bean 初始化过程中 “提前曝光” 半成品对象,打破 “互相等待对方完成” 的死循环。

先明确前提

Spring 只能解决 “单例 Bean 的属性注入循环依赖”(如 @Autowired 字段注入),无法解决 原型 Bean 或 构造器注入 的循环依赖(这两种情况会直接抛出异常)。

核心:三级缓存的作用

Spring 维护了三个缓存(本质是 Map),用于存储不同状态的 Bean:

缓存名称作用(存储内容)
singletonObjects一级缓存:存储 完全初始化完成 的单例 Bean(可以直接使用的成品)。
earlySingletonObjects二级缓存:存储 提前暴露的半成品 Bean(已实例化,但未完成属性注入和初始化方法)。
singletonFactories三级缓存:存储 Bean 工厂对象(用于生成半成品 Bean 的工厂,含 AOP 代理逻辑)。

解决循环依赖的完整流程(举例:A 依赖 B,B 依赖 A)

假设我们有两个单例 Bean:

@Service
public class A {
    @Autowired
    private B b; // A 依赖 B
}

@Service
public class B {
    @Autowired
    private A a; // B 依赖 A
}

Spring 创建这两个 Bean 的过程如下,完美打破循环:

1. 开始创建 Bean A
  • 步骤 1:实例化 A
    Spring 先通过构造器创建 A 的对象(只完成 “实例化”,还没注入属性 b,也没执行初始化方法)。此时 A 是 “半成品”。
  • 步骤 2:暴露 A 的工厂到三级缓存
    实例化后,Spring 会创建一个 “Bean 工厂”(ObjectFactory),并放入三级缓存 singletonFactories。这个工厂的作用是:当其他 Bean 需要依赖 A 时,能通过工厂获取 A 的半成品(如果 A 需要被代理,工厂会生成代理对象)。
    // 伪代码:放入三级缓存
    singletonFactories.put("a", () -> createEarlyBeanReference(a));
    
  • 步骤 3:A 需要注入 B
    接下来 Spring 要给 A 注入属性 b,但此时一级缓存中没有 B(B 还没创建),所以 Spring 暂停 A 的初始化,转而开始创建 B。
2. 开始创建 Bean B
  • 步骤 1:实例化 B
    同样,先通过构造器创建 B 的对象(半成品,未注入属性 a)。
  • 步骤 2:暴露 B 的工厂到三级缓存
    把 B 的工厂放入 singletonFactories,方便其他 Bean 依赖 B 时获取。
  • 步骤 3:B 需要注入 A
    现在 B 要注入属性 a,Spring 会依次从缓存中找 A:
    • 一级缓存 singletonObjects:没有(A 还没完成初始化)。
    • 二级缓存 earlySingletonObjects:没有(A 还没被提前暴露到这里)。
    • 三级缓存 singletonFactories:找到了 A 的工厂!
      此时,Spring 会通过 A 的工厂获取 A 的半成品(如果 A 需要 AOP 代理,这里会生成代理对象),并把 A 从三级缓存移到二级缓存 earlySingletonObjects(标记为 “已提前暴露”)。
  • 步骤 4:B 完成初始化
    B 成功注入 A 的半成品后,继续执行其他初始化操作(如 @PostConstruct 方法),最终成为 “成品”,放入一级缓存 singletonObjects,并从二、三级缓存中移除。
3. 回到 Bean A 的初始化
  • B 已经是成品(在一级缓存中),所以 A 可以顺利注入 B。
  • A 继续执行剩余初始化操作,最终成为成品,放入一级缓存 singletonObjects,并从二、三级缓存中移除。

为什么需要三级缓存?

核心是为了处理 AOP 代理场景
如果 A 被 @Transactional 等注解标记,需要生成代理对象(而非原始对象),那么 B 依赖的应该是 A 的代理对象,而不是原始对象。
三级缓存中的 “Bean 工厂” 会在获取 A 时,判断是否需要生成代理对象,确保依赖注入的是正确的代理对象。如果只有二级缓存,无法动态生成代理(只能存固定对象),会导致依赖注入错误。

无法解决的情况

  1. 原型 Bean 循环依赖:原型 Bean 每次获取都会重新创建,不会放入缓存,所以无法提前暴露,直接抛出 BeanCurrentlyInCreationException
  2. 构造器注入循环依赖:构造器注入在 “实例化阶段” 就需要依赖(如 A(B b) 和 B(A a)),而此时 Bean 还未实例化,无法提前暴露,会抛出异常(可通过 @Lazy 注解延迟加载依赖解决)。

总结

Spring 解决单例 Bean 循环依赖的核心是:
在 Bean 实例化后、初始化前,通过三级缓存提前暴露半成品对象,让依赖方可以先获取到这个半成品,从而打破循环等待。三级缓存的设计还兼顾了 AOP 代理的动态生成,确保依赖注入的是正确的对象(原始对象或代理对象)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值