当前位置: 首页 > news >正文

Spring Bean的创建过程与三级缓存的关系详解

以下以 Bean A 和 Bean B 互相依赖为例,结合源码和流程图,详细说明 Bean 的创建过程与三级缓存的交互。


1. Bean 的完整生命周期(简化版)

实例化
填充属性
初始化
存入一级缓存

2. 三级缓存的作用

缓存名称存储内容目的
singletonObjects完全初始化好的 Bean(成品)直接对外提供可用的 Bean
earlySingletonObjects未初始化完成的 Bean(半成品,早期引用)临时存储,供其他 Bean 提前引用,避免循环依赖卡死
singletonFactories创建 Bean 的 ObjectFactory(工厂对象)延迟生成早期引用(支持 AOP 代理等动态扩展)

3. 详细流程(以 Bean A → Bean B → Bean A 循环依赖为例)

步骤 1:开始创建 Bean A
  • 入口:调用 getBean("a")
  • 阶段:实例化(createBeanInstance())。
    // 反射调用无参构造函数创建原始对象
    A a = new A(); 
    
  • 此时状态
    • a 是原始对象,属性 bnull
    • 未存入任何缓存

步骤 2:提前暴露 Bean A 的工厂
  • 操作:将 Bean A 的 ObjectFactory 存入 三级缓存singletonFactories)。
    // AbstractAutowireCapableBeanFactory.doCreateBean()
    addSingletonFactory("a", () -> getEarlyBeanReference("a", mbd, a));
    
  • 目的:允许其他 Bean(如 Bean B)在依赖注入时,通过工厂获取 Bean A 的早期引用(可能是代理对象)。

步骤 3:填充 Bean A 的属性(发现依赖 Bean B)
  • 操作:调用 populateBean("a"),检测到 a 依赖 b
  • 触发:调用 getBean("b") 创建 Bean B。

步骤 4:开始创建 Bean B
  • 入口:调用 getBean("b")
  • 阶段:实例化(createBeanInstance())。
    B b = new B(); // 反射创建原始对象
    
  • 此时状态
    • b 是原始对象,属性 anull
    • 未存入任何缓存

步骤 5:提前暴露 Bean B 的工厂
  • 操作:将 Bean B 的 ObjectFactory 存入 三级缓存
    addSingletonFactory("b", () -> getEarlyBeanReference("b", mbd, b));
    

步骤 6:填充 Bean B 的属性(发现依赖 Bean A)
  • 操作:调用 populateBean("b"),检测到 b 依赖 a
  • 触发:再次调用 getBean("a")

步骤 7:再次获取 Bean A(解决循环依赖)
  • 检查一级缓存singletonObjects 中没有 Bean A。
  • 检查二级缓存earlySingletonObjects 中没有 Bean A。
  • 检查三级缓存:找到 Bean A 的 ObjectFactory
  • 操作:调用 ObjectFactory.getObject(),实际执行 getEarlyBeanReference()
    // 生成早期引用(可能是代理对象)
    Object earlyA = getEarlyBeanReference("a", mbd, a);
    
    • 若 Bean A 需要 AOP 代理:此时生成代理对象(如 A$$EnhancerBySpringCGLIB)。
    • 若无需代理:直接返回原始对象 a
  • 升级缓存:将早期引用 earlyA 存入 二级缓存earlySingletonObjects),并清除三级缓存中的工厂。
    this.earlySingletonObjects.put("a", earlyA);
    this.singletonFactories.remove("a");
    
  • 结果:Bean B 获得 Bean A 的早期引用(earlyA),完成属性注入 b.setA(earlyA)

步骤 8:完成 Bean B 的初始化
  • 初始化:执行 initializeBean("b")(包括 @PostConstruct、AOP 代理等)。
  • 存入一级缓存:将完全初始化的 Bean B 存入 singletonObjects
    registerSingleton("b", b);
    

步骤 9:回到 Bean A 的属性填充
  • 操作:Bean A 获得完全初始化的 Bean B(b),完成属性注入 a.setB(b)

步骤 10:完成 Bean A 的初始化
  • 初始化:执行 initializeBean("a")
    • 若 Bean A 需要代理:此时生成代理对象 proxyA(覆盖原始对象 a)。
    • 若无需代理:直接使用原始对象 a
  • 存入一级缓存:将最终 Bean A(可能是代理对象)存入 singletonObjects,并清除二、三级缓存。
    registerSingleton("a", a); // 或 proxyA
    

4. 三级缓存的交互总结

Bean A Bean B 一级缓存 二级缓存 三级缓存 存入 ObjectFactory (A) 触发创建 Bean B 存入 ObjectFactory (B) 触发获取 Bean A 通过 ObjectFactory 生成早期引用 升级到二级缓存 (A 的早期引用) 完成初始化,存入 Bean B 完成初始化,存入 Bean A Bean A Bean B 一级缓存 二级缓存 三级缓存

5. 关键设计思想

  1. 提前暴露半成品:允许未初始化的 Bean 被其他 Bean 引用,打破循环依赖的死锁。
  2. 动态代理兼容:通过 ObjectFactory 延迟生成早期引用,确保 AOP 代理逻辑正确执行。
  3. 缓存层级隔离
    • 一级缓存:存放完全可用的 Bean(安全)。
    • 二级缓存:临时存放早期引用(加速依赖查找)。
    • 三级缓存:存放工厂,支持动态扩展(如代理)。

6. Bean的创建是否都需要经历三级缓存


1. 必须经历三级缓存的场景

条件:当 Bean 是单例(Singleton)且 存在循环依赖(通过属性注入)时,Spring 会通过三级缓存机制解决依赖问题。此时 Bean 的创建流程如下:

graph LR
    A[实例化 Bean] --> B[注册 ObjectFactory 到三级缓存]
    B --> C[填充属性(触发循环依赖)]
    C --> D[从三级缓存升级到二级缓存]
    D --> E[完成初始化后存入一级缓存]
示例:Bean A 和 Bean B 互相依赖
  • 步骤
    1. 创建 Bean A 时,实例化后注册 ObjectFactory 到三级缓存。
    2. 注入 Bean B 时触发 B 的创建。
    3. 创建 Bean B 时,实例化后注册 ObjectFactory 到三级缓存。
    4. 注入 Bean A 时,通过三级缓存获取 A 的早期引用(升级到二级缓存)。
    5. Bean B 完成初始化后存入一级缓存。
    6. Bean A 完成初始化后存入一级缓存。

2. 不经历三级缓存的场景
场景 1:无循环依赖的普通 Bean

条件:Bean 是单例,且 没有循环依赖(如 Bean C 无依赖或依赖已存在的 Bean)。

流程

实例化 Bean
直接填充属性
初始化
存入一级缓存

关键点

  • 不需要提前暴露早期引用,直接跳过三级缓存。
  • 例如:Bean C 依赖的 Bean D 已经在一级缓存中,则直接注入 D,无需触发缓存升级。

场景 2:构造器注入的循环依赖

条件:Bean 使用 构造器注入 导致循环依赖。

结果
Spring 无法解决构造器注入的循环依赖,直接抛出 BeanCurrentlyInCreationException
原因

  • 构造器注入需在实例化阶段完成依赖注入,此时 Bean 尚未创建完成,无法提前暴露到三级缓存。

场景 3:原型(Prototype)作用域的 Bean

条件:Bean 的作用域为 prototype

结果
Spring 不缓存原型 Bean,每次请求都创建新实例,因此:

  • 不涉及三级缓存。
  • 循环依赖直接报错(原型 Bean 不支持循环依赖)。

场景 4:无需代理的 Bean

条件:Bean 不需要 AOP 代理,且无循环依赖。

流程
直接通过反射创建原始对象,完成初始化后存入一级缓存,全程不涉及三级缓存。


3. 三级缓存的触发条件总结
条件是否触发三级缓存示例场景
单例 + 属性注入 + 循环依赖✔️Bean A → Bean B → Bean A
单例 + 无循环依赖普通 Service 类
单例 + 构造器注入循环依赖❌(直接报错)构造器注入导致循环依赖
原型作用域 Bean每次请求新实例
需要代理但无循环依赖独立 Bean 使用 @Transactional

4. 源码验证
(1) 三级缓存的注册逻辑

AbstractAutowireCapableBeanFactory.doCreateBean() 中,只有满足以下条件时才会注册 ObjectFactory

// 条件:单例 + 允许循环引用 + Bean 正在创建中
boolean earlySingletonExposure = (mbd.isSingleton() && 
    this.allowCircularReferences && 
    isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
(2) 无循环依赖时的跳过逻辑

若 Bean 无循环依赖,则不会触发从三级缓存获取早期引用的代码:

// DefaultSingletonBeanRegistry.getSingleton()
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
    // 无循环依赖时,不会进入此分支
    if (isSingletonCurrentlyInCreation(beanName)) {
        // 从三级缓存获取早期引用的逻辑...
    }
}

总结
  • 必须经历三级缓存:仅当单例 Bean 存在属性注入的循环依赖时。
  • 不经历三级缓存
    • 无循环依赖的单例 Bean。
    • 构造器注入的循环依赖(直接报错)。
    • 原型作用域 Bean。
    • 不需要代理的普通 Bean。

7. 常见问题解答

Q1:为什么需要三级缓存?二级缓存不够吗?
  • 三级缓存的核心价值:解耦 Bean 的创建代理的生成
    • 如果只有二级缓存:
      代理逻辑需在实例化后立即执行(违反 Spring 的设计原则,代理应在初始化阶段完成)。
    • 三级缓存通过 ObjectFactory 延迟代理生成,确保代理逻辑在正确的时机执行。
Q2:构造器注入为何无法解决循环依赖?
  • 根本原因:构造器注入需在实例化阶段完成依赖注入,而实例化尚未完成时无法提前暴露对象(三级缓存机制无法介入)。
Q3:为什么二级缓存叫 earlySingletonObjects
  • 它存储的是“早期单例对象”(尚未完成初始化),与一级缓存的“完全体单例”区分。

通过以上流程,可以清晰理解 Spring 如何通过三级缓存协作,在保证单例完整性的前提下,优雅解决循环依赖问题。

相关文章:

  • socket到底是什么
  • 分发饼干问题——用贪心算法解决
  • Oracle 11G RAC 删除添加节点(一):删除节点
  • 智能SEO关键词AI精准布局
  • swagger 注释说明
  • LeetCode 34 在排序数组中查找元素的第一个和最后一个位置
  • 【5G学习】5G中常说的上下文之上下文响应
  • 在线地图支持天地图和腾讯地图,仪表板和数据大屏支持发布功能,DataEase开源BI工具v2.10.7 LTS版本发布
  • java中的Future的设计模式 手写一个简易的Future
  • C语言 ——— 认识C语言
  • 应对海量数据归档难题?AWS Glacier 的低成本冷存储解决方案实践指南
  • Keras使用1
  • 【AI学习从零至壹】语⾔模型及词向量相关知识
  • linux多线(进)程编程——(6)共享内存
  • 链表代码实现(C++)
  • C语言--常见的编程示例
  • 医药采购系统平台第5天01:药品目录导入功能的实现Oracle触发器的定义供货商药品目录模块的分析供货商目录表和供货商控制表的分析、数据模型设计和优化
  • Rasa 模拟实现超简易医生助手(适合初学练手)
  • Tkinter表格与列表框应用
  • 制作像素风《饥荒》类游戏的整体蓝图和流程
  • 中国科学院院士张泽民已任重庆医科大学校长
  • 王毅:时代不容倒退,公道自在人心
  • 笔墨如何“构城”?上海美院城市山水晋京展出
  • 海尔智家一季度营收791亿元:净利润增长15%,海外市场收入增超12%
  • 解读|特朗普“助攻”下加拿大自由党“惨胜”,卡尼仍需克服“特鲁多阴影”
  • 长三角议事厅·周报|长三角游戏出海,关键在“生态输出”