Spring循环依赖三级缓存如此easy!!

Spring 解决循环依赖的核心原理是 三级缓存(Three-Level Cache) 机制,结合 提前暴露未完全初始化的 Bean 实例。以下是深入分析其实现原理的关键步骤和设计思想:


一、循环依赖的典型场景

假设两个 Bean 互相依赖:

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

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

此时,Spring 需要解决 A 依赖 B → B 依赖 A 的循环依赖问题。


二、三级缓存的核心机制

Spring 通过三个缓存层解决单例(Singleton)Bean 的循环依赖:

缓存级别存储内容作用
一级缓存 singletonObjects完全初始化好的 Bean 实例最终对外暴露的 Bean
二级缓存 earlySingletonObjects提前暴露的 Bean 实例(未完成属性注入和初始化)解决循环依赖的中间状态
三级缓存 singletonFactoriesBean 的工厂对象(ObjectFactory生成早期 Bean 实例(可能包含代理对象)

三、解决循环依赖的详细流程

以下以 A → B → A 的循环依赖为例,解析 Spring 的处理过程:

  1. 创建 Bean A 的实例

    • 调用 A 的构造函数,创建一个原始对象(尚未注入属性)。

    • 将 A 的原始对象包装为 ObjectFactory,存入 三级缓存singletonFactories)。

      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
      
  2. 为 Bean A 注入属性 B

    • Spring 发现 A 依赖 B,开始创建 B 的实例。
  3. 创建 Bean B 的实例

    • 调用 B 的构造函数,创建一个原始对象。
    • 将 B 的原始对象包装为 ObjectFactory,存入 三级缓存
  4. 为 Bean B 注入属性 A

    • Spring 发现 B 依赖 A,尝试从缓存中获取 A
      1. 检查 一级缓存singletonObjects):无。
      2. 检查 二级缓存earlySingletonObjects):无。
      3. 从三级缓存 中取出 AObjectFactory,调用 getObject() 生成早期引用(可能是原始对象或代理对象)。
      4. 将生成的早期引用 A 提升到 二级缓存
    • 将早期引用 A 注入到 B 的属性中。
    • 完成 B 的属性注入和初始化,将 B 放入 一级缓存
  5. 完成 Bean A 的初始化

    • 将完全初始化好的 B 注入到 A 中。
    • 执行 A 的初始化方法(如 @PostConstruct)。
    • A二级缓存 移动到 一级缓存

四、关键源码解析(简化版)

1. DefaultSingletonBeanRegistry 中的缓存定义
public class DefaultSingletonBeanRegistry {
    // 一级缓存:完全初始化的 Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    // 二级缓存:未完全初始化的早期 Bean
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
    // 三级缓存:生成早期 Bean 的工厂
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
}
2. AbstractAutowireCapableBeanFactory 中的依赖注入逻辑
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 1. 创建 Bean 实例(未填充属性)
    Object instance = createBeanInstance(beanName, mbd, args);
    
    // 2. 将 Bean 的工厂对象添加到三级缓存(解决循环依赖的关键)
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, instance));
    
    // 3. 属性注入(可能触发依赖 Bean 的创建)
    populateBean(beanName, mbd, instance);
    
    // 4. 初始化 Bean(调用初始化方法)
    initializeBean(beanName, instance, mbd);
    return instance;
}

五、解决循环依赖的条件与限制

  1. 必须是单例 Bean
    原型(Prototype)作用域的 Bean 无法解决循环依赖,因为 Spring 不缓存原型 Bean。

  2. 依赖注入方式必须是字段注入或 Setter 注入

    • 构造器注入无法解决循环依赖,因为构造器调用时 Bean 尚未创建完成,无法存入三级缓存。

    • 示例错误场景:

      @Component
      public class A {
          public A(B b) {} // 构造器注入会导致循环依赖无法解决
      }
      
  3. Bean 未被 AOP 代理提前增强
    如果 Bean 需要通过 AOP 生成代理对象,Spring 会通过 getEarlyBeanReference() 方法在三级缓存中提前生成代理对象。


六、设计思想总结

  1. 空间换时间
    通过三级缓存暂存中间状态,避免重复创建对象。
  2. 提前暴露引用
    允许未完全初始化的对象被其他 Bean 引用,打破循环依赖的死锁。
  3. 分离实例化与初始化
    将 Bean 的创建过程分解为多个阶段,确保依赖链的逐步构建。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小伍的Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值