Spring的三级缓存如何解决单例Bean循环依赖

Spring的三级缓存是解决单例Bean循环依赖的核心机制。循环依赖指两个或多个Bean相互依赖(如A依赖B,B依赖A),若不处理会导致实例化过程陷入无限循环。三级缓存通过"提前暴露Bean的早期引用",让依赖在Bean完全初始化前即可被访问,从而打破循环。

一、三级缓存的定义与作用

Spring在DefaultSingletonBeanRegistry中定义了三级缓存,均为Map结构,存储单例Bean的不同状态:

缓存级别 变量名 类型 作用
一级缓存 singletonObjects Map<String, Object> 存储完全初始化完成的单例Bean(最终可用的Bean)。
二级缓存 earlySingletonObjects Map<String, Object> 存储提前暴露的未完全初始化的Bean(仅实例化未初始化,用于临时引用)。
三级缓存 singletonFactories Map<String, ObjectFactory<?>> 存储Bean的工厂对象,用于在需要时生成Bean的早期引用(支持AOP代理)。

二、循环依赖的解决过程(以A依赖B,B依赖A为例)

假设场景:AB都是单例Bean,A的字段依赖BB的字段依赖A(字段注入,非构造器注入)。流程如下:

步骤1:实例化A,暴露工厂到三级缓存
  1. 容器启动时,首先尝试获取AgetBean(A))。
  2. 发现A不在一级缓存中,进入创建流程:
    • 实例化A :调用A的构造器,生成A的原始对象(未设置属性,未执行初始化方法)。

    • 暴露工厂到三级缓存 :创建ObjectFactory(工厂对象),用于在需要时生成A的早期引用(若A需要AOP代理,工厂会生成代理对象;否则直接返回原始对象)。

      java 复制代码
      // 简化逻辑:添加到三级缓存
      singletonFactories.put("A", () -> getEarlyBeanReference("A", beanDefinition, rawA));
    • 此时A的状态:仅实例化,未初始化(属性未填充),存在于三级缓存的工厂中。

步骤2:A填充属性时依赖B,触发B的创建
  1. A进入"属性填充"阶段,发现依赖B,于是调用getBean(B)
  2. 容器尝试获取B,发现B不在一级缓存中,进入创建流程:
    • 实例化B :调用B的构造器,生成B的原始对象。
    • 暴露工厂到三级缓存 :同A,将B的工厂放入三级缓存。
    • 此时B的状态:仅实例化,未初始化,存在于三级缓存。
步骤3:B填充属性时依赖A,从三级缓存获取A的早期引用
  1. B进入"属性填充"阶段,发现依赖A,调用getBean(A)
  2. 容器查找A
    • 一级缓存(singletonObjects):无(A未完全初始化)。
    • 二级缓存(earlySingletonObjects):无。
    • 三级缓存(singletonFactories):存在A的工厂。
  3. 获取A的早期引用
    • 调用A的工厂对象,生成A的早期引用(若A需要代理,则此处生成代理对象;否则为原始对象)。
    • A的早期引用从三级缓存移到二级缓存(earlySingletonObjects.put("A", earlyA)),并删除三级缓存中的工厂(避免重复生成)。
  4. 注入A到B :将A的早期引用(earlyA)注入到B的属性中。
步骤4:B完成初始化,放入一级缓存
  1. B的属性填充完成,进入"初始化"阶段(执行@PostConstructafterPropertiesSet()等)。

  2. B完全初始化后,从二级缓存(若有)或直接放入一级缓存:

    java 复制代码
    singletonObjects.put("B", b); // B成为可用Bean
    earlySingletonObjects.remove("B"); // 清除二级缓存
    singletonFactories.remove("B"); // 清除三级缓存
步骤5:A获取B的引用,完成初始化
  1. A的属性填充阶段继续:此时B已在一级缓存中,直接从singletonObjects获取B,注入到A的属性中。

  2. A进入"初始化"阶段(执行初始化方法)。

  3. A完全初始化后,放入一级缓存:

    java 复制代码
    singletonObjects.put("A", a); // A成为可用Bean
    earlySingletonObjects.remove("A"); // 清除二级缓存

三、关键细节:为什么需要三级缓存?

核心原因是支持AOP代理

  • 若Bean需要被AOP代理(如被@Transactional注解),最终注入给依赖的应该是代理对象,而非原始对象。
  • 三级缓存的ObjectFactory的作用是:在需要时(如被其他Bean依赖时)才生成代理对象,而非在实例化后立即生成。
  • 若只有二级缓存(直接存储早期引用),无法动态生成代理对象(实例化时还不确定是否需要代理,代理逻辑可能依赖其他Bean)。

四、无法解决的循环依赖:构造器注入

三级缓存仅能解决字段注入setter注入 的循环依赖,无法解决构造器注入的循环依赖。原因是:

  • 构造器注入在实例化阶段 就需要依赖对象(调用构造器时必须传入参数),而三级缓存是在实例化后才暴露早期引用。
  • 例如:A的构造器依赖BB的构造器依赖A,此时AB在实例化时就需要对方,而缓存中尚无任何引用,导致循环依赖无法解决(抛出BeanCurrentlyInCreationException)。

总结

三级缓存通过"提前暴露Bean的工厂→按需生成早期引用→逐步完成初始化"的流程,打破了单例Bean的循环依赖。其中:

  • 三级缓存(singletonFactories)负责延迟生成早期引用(支持AOP代理)。
  • 二级缓存(earlySingletonObjects)负责临时存储早期引用,避免重复生成。
  • 一级缓存(singletonObjects)存储最终可用的完全初始化Bean。

这一机制既保证了循环依赖的解决,又兼顾了AOP代理等复杂场景的正确性。

相关推荐
Rain的Java大神之路3 分钟前
Docker搭建Redis集群完全指南
java·运维·redis·后端·docker·容器·架构
Raas10010 分钟前
大模型网关和API网关区别是什么?MAI Gateway统一AI流量治理
java·大数据·运维·人工智能·gateway·企业级·ai网关
BioRunYiXue19 分钟前
RACE技术全攻略:从全长克隆到靶基因验证
java·javascript·网络·人工智能·科技·算法·eclipse
AC赳赳老秦29 分钟前
个保法下数据处理:OpenClaw 自动过滤公开数据中的个人信息,保障采集分析合规性
java·python·sqlite·json·php·deepseek·openclaw
rannn_11135 分钟前
Java 后端面试题总结:点赞功能怎么实现?
java·后端·面试
CodeStats1 小时前
【Java进程通信】Java进程通信系统完全指南:从ProcessBuilder底层原理到多语言实战
java·前端·python·进程·ai编程·processbuilder
绿智校园1 小时前
物联网平台选型指南:为什么底层协议自研比云端SDK接入更可靠?
java·物联网·struts
T01156181 小时前
艺培场馆课时预约小程序用户端 & 后台联动踩坑上篇(预约、课时、缓存、消息、学员端自身问题)
java·缓存·小程序·bug·全栈项目实战手记·艺培课时系统‘’
长谷深风1111 小时前
AI Agent 踩坑:盲目重试会把小故障炸成系统灾难
java·大数据·ai·agent·ai agent·工具调用·agent设计
evans在进步1 小时前
Spring Boot 核心机制详解:自动装配、事件监听、异步任务与请求映射
java·spring boot·后端