Spring循环依赖与三级缓存:原理剖析与记忆技巧

98 阅读2分钟

Spring循环依赖与三级缓存:原理剖析与记忆技巧

一、循环依赖问题复现

场景描述

当两个Bean相互依赖时,Spring容器初始化会触发循环依赖问题:

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

异常现象

控制台报错:Requested bean is currently in creation: Is there an unresolvable circular reference?

二、三级缓存原理剖析

缓存结构(DefaultSingletonBeanRegistry)

  1. 一级缓存 singletonObjects:存放完整初始化的Bean
  2. 二级缓存 earlySingletonObjects:存放早期暴露的原始对象
  3. 三级缓存 singletonFactories:存放对象工厂(ObjectFactory)

Bean创建流程

  1. 创建ServiceA实例

    • 实例化后立即将工厂对象存入三级缓存:因此第三步B实例可以从三级缓存取到A实例的工厂
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    
  2. 属性注入ServiceB

    • 触发ServiceB的创建流程
  3. 创建ServiceB实例

    • 同样将工厂存入三级缓存
    • 注入ServiceA时触发三级缓存升级:
      graph TD
      B_填充A属性 --> 检查一级缓存
      一级缓存无 --> 检查二级缓存
      二级缓存无 --> 从三级缓存获取工厂
      执行工厂方法 --> 原始A对象存入二级缓存
      删除三级缓存记录
      
  4. 完成ServiceB初始化

    • ServiceB进入一级缓存
  5. 继续完成ServiceA初始化

    • 从二级缓存获取原始对象
    • 执行后续初始化步骤
    • 最终ServiceA进入一级缓存

三、三级缓存记忆技巧

形象比喻

  • 一级缓存:成品仓库(完整可用的Bean)
  • 二级缓存:半成品货架(已实例化未初始化的Bean)
  • 三级缓存:工厂流水线(对象生产车间)

记忆口诀

一级成品直接拿
二级半成品凑合用
三级工厂保平安
循环依赖全靠它

四、设计思想总结

  1. 空间换时间:通过缓存中间状态避免重复创建
  2. 提前暴露引用:允许未完成初始化的对象被引用
  3. 代理对象处理:通过ObjectFactory延迟生成代理对象
  4. 状态隔离:三级缓存确保不同状态对象互不干扰

注意事项:构造器注入无法通过三级缓存解决循环依赖,建议优先使用属性注入方式