【Spring源码】getBean源码实战总结

getBean源码实战总结

代码仓库 :Gitee 仓库链接

本文档是对 getBean 源码实战系列文章的总结,建议先阅读完整的系列文章后再阅读本文。

回顾整个系列

从getBean源码实战(一)开始,我们一步步深入 Spring 的 getBean() 方法,探索了 Bean 获取的完整流程。现在让我们回顾一下整个系列的核心内容:

系列文章概览

  1. (一)基础流程 - 最简单的 Bean 获取,理解 doGetBean 的基本流程
  2. (二)别名解析 - 通过别名获取已缓存的 Bean,理解别名转换和一级缓存
  3. (三)循环依赖 - 单例 Bean 的循环依赖处理,理解三级缓存机制
  4. (四)FactoryBean - FactoryBean 的特殊处理,理解工厂 Bean 的机制
  5. (五)MergedBeanDefinition - Bean 定义合并,理解父子 Bean 定义的继承
  6. (六)BeanAware - Aware 接口回调机制,理解 Bean 如何感知容器信息

💡 关键理解 :这六篇文章从不同角度深入分析了 getBean() 方法,每一篇都聚焦一个核心机制,但它们是相互关联的,共同构成了 Spring IoC 容器的 Bean 获取机制。

getBean 的完整流程

让我们从整体上理解 getBean() 的完整流程,这是整个系列的核心:

核心流程图

复制代码
getBean(name)
    ↓
doGetBean(name, requiredType, args, typeCheckOnly)
    ↓
【步骤1】transformedBeanName(name) - 翻译 Bean 名称
    ├─ 处理 FactoryBean 前缀 (&)
    └─ 处理别名转换 (canonicalName)
    ↓
【步骤2】getSingleton(beanName) - 从缓存获取单例
    ├─ 一级缓存:singletonObjects(完全初始化的单例)
    ├─ 二级缓存:earlySingletonObjects(早期引用)
    └─ 三级缓存:singletonFactories(ObjectFactory)
    ↓
【步骤3】getMergedLocalBeanDefinition(beanName) - 获取合并后的 Bean 定义
    ├─ 处理父子 Bean 定义继承
    └─ 合并属性、依赖等
    ↓
【步骤4】检查依赖关系 (dependsOn)
    ↓
【步骤5】根据作用域创建 Bean
    ├─ 单例 (singleton)
    │   ├─ getSingleton(beanName, singletonFactory)
    │   │   ├─ 标记为正在创建
    │   │   ├─ 创建 Bean 实例 (createBean)
    │   │   │   ├─ doCreateBean
    │   │   │   │   ├─ 实例化 (createBeanInstance)
    │   │   │   │   ├─ 提前暴露(放入三级缓存)
    │   │   │   │   ├─ 属性注入 (populateBean)
    │   │   │   │   │   └─ 触发依赖 Bean 的创建(可能形成循环依赖)
    │   │   │   │   └─ 初始化 (initializeBean)
    │   │   │   │       ├─ invokeAwareMethods(Aware 接口)
    │   │   │   │       ├─ BeanPostProcessor(前置处理)
    │   │   │   │       ├─ invokeInitMethods(初始化方法)
    │   │   │   │       └─ BeanPostProcessor(后置处理)
    │   │   │   └─ 放入一级缓存
    │   │   └─ 从缓存中移除
    │   └─ 返回单例 Bean
    ├─ 原型 (prototype)
    │   └─ 每次创建新实例
    └─ 其他作用域(request、session 等)
    ↓
【步骤6】getObjectForBeanInstance - 处理 FactoryBean
    ├─ 判断是否需要获取 FactoryBean 本身
    └─ 获取 FactoryBean 创建的对象
    ↓
返回 Bean 实例

🔍 观察:这个流程看似复杂,但实际上每一步都有其明确的目的。让我们深入理解每个关键环节。

核心机制深度解析

1. 名称转换机制

核心方法 :transformedBeanName() 和 canonicalName()

java 复制代码
protected String transformedBeanName(String name) {
    return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

💡 关键理解:名称转换分为两步:

  1. 处理 FactoryBean 前缀 :&beanName → beanName
  2. 处理别名转换 :alias → canonicalName(支持链式别名)

设计思想:

  • 统一入口:所有名称转换都通过这个方法,保证一致性
  • 链式别名支持:别名可以指向另一个别名,最终指向 Bean 名称
  • FactoryBean 特殊处理 :通过 & 前缀区分获取 FactoryBean 本身还是其创建的对象

2. 三级缓存机制

核心缓存:

  • 一级缓存(singletonObjects):完全初始化的单例 Bean
  • 二级缓存(earlySingletonObjects):早期引用,用于解决循环依赖
  • 三级缓存(singletonFactories):ObjectFactory,用于创建早期引用

💡 关键理解:三级缓存是 Spring 解决单例循环依赖的核心机制。

为什么需要三级缓存?

让我们思考一下:

如果只有一级缓存:

  • 问题:Bean A 依赖 Bean B,Bean B 依赖 Bean A
  • 当创建 A 时,需要注入 B,但 B 还没创建
  • 当创建 B 时,需要注入 A,但 A 还没完全初始化
  • 结果:无法解决循环依赖

如果只有一级和二级缓存:

  • 问题:无法处理 AOP 代理的情况
  • 当 Bean 需要 AOP 代理时,早期引用应该是代理对象,而不是原始对象
  • 结果:无法正确处理需要代理的循环依赖

三级缓存的作用:

  • 延迟创建早期引用:只有在真正需要时才通过 ObjectFactory 创建
  • 支持 AOP 代理:ObjectFactory 中可以应用 BeanPostProcessor,创建代理对象
  • 性能优化:不需要代理的 Bean 不会创建代理对象

🔍 发现:三级缓存的设计体现了 Spring 在功能性和性能之间的精妙平衡。

3. 循环依赖解决机制

核心流程(以 ServiceA 和 ServiceB 为例):

  1. 创建 ServiceA

    • 实例化:new ServiceA()
    • 提前暴露:将 ObjectFactory 放入三级缓存
    • 属性注入:需要注入 ServiceB,触发 getBean("serviceB")
  2. 创建 ServiceB

    • 实例化:new ServiceB()
    • 提前暴露:将 ObjectFactory 放入三级缓存
    • 属性注入:需要注入 ServiceA,触发 getBean("serviceA")
    • 关键:此时从三级缓存中获取 ServiceA 的早期引用
    • 将早期引用放入二级缓存,从三级缓存中移除
    • 完成 ServiceB 的初始化
  3. 完成 ServiceA

    • 从二级缓存中获取 ServiceB(已完全初始化)
    • 完成 ServiceA 的初始化
    • 将 ServiceA 放入一级缓存,从二级缓存中移除

💡 关键理解:循环依赖的解决依赖于"提前暴露"机制。Bean 在完全初始化之前就被放入缓存,供其他 Bean 使用。

⚠️ 限制 :三级缓存机制只适用于单例 Bean 的属性注入方式的循环依赖。构造函数注入的循环依赖无法解决。

4. FactoryBean 机制

核心方法 :getObjectForBeanInstance()

💡 关键理解 :FactoryBean 是一种特殊的 Bean,它本身是一个工厂,用于创建另一个对象。

两种获取方式:

  1. 获取 FactoryBean 创建的对象 :getBean("connection")

    • 去掉 & 前缀(如果有)
    • 检查是否是 FactoryBean
    • 调用 factoryBean.getObject() 获取对象
  2. 获取 FactoryBean 本身 :getBean("&connection")

    • 保留 & 前缀
    • 直接返回 FactoryBean 实例

🔍 设计思想:

  • 工厂模式:FactoryBean 实现了工厂模式,将对象创建的复杂性封装在工厂中
  • 统一接口:所有 FactoryBean 都实现同一个接口,便于统一处理
  • 特殊标识 :通过 & 前缀区分获取工厂本身还是工厂创建的对象

5. MergedBeanDefinition 机制

核心方法 :getMergedBeanDefinition()

💡 关键理解:MergedBeanDefinition 是将子 Bean 定义和父 Bean 定义合并后得到的完整 Bean 定义。

为什么需要合并?

  1. Bean 定义继承:子 Bean 可以继承父 Bean 的配置
  2. 属性覆盖:子 Bean 可以覆盖父 Bean 的属性值
  3. 抽象 Bean:抽象 Bean 不能直接实例化,但可以作为父 Bean 被继承

合并流程:

复制代码
获取子 Bean 定义
    ↓
检查是否有 parentBeanName
    ↓
如果有,递归获取父 Bean 定义(可能来自 parentBeanFactory)
    ↓
使用 overrideFrom() 合并属性
    ↓
缓存合并后的 Bean 定义
    ↓
返回 RootBeanDefinition

🔍 发现:合并机制体现了 Spring 对配置复用的支持,通过继承减少重复配置。

6. BeanAware 回调机制

核心方法 :invokeAwareMethods()

💡 关键理解:Aware 接口允许 Bean 在初始化时获取容器信息,通过回调机制实现。

调用时机:

  • 在属性注入之后
  • 在初始化方法之前

支持的 Aware 接口:

  • BeanNameAware:获取 Bean 名称
  • BeanFactoryAware:获取 BeanFactory 容器
  • BeanClassLoaderAware:获取类加载器
  • ApplicationContextAware:获取 ApplicationContext(通过 BeanPostProcessor 处理)

设计思想:

  • 回调模式:容器主动通知 Bean,而不是 Bean 主动查询
  • 接口隔离:每个 Aware 接口只负责一种信息
  • 可选实现:Bean 可以选择性地实现需要的接口

Spring 的设计思想总结

通过整个系列的学习,我们发现了 Spring 框架中几个重要的设计思想:

1. 模板方法模式

doGetBean() 方法定义了 Bean 获取的算法骨架,但每个步骤都可以通过扩展机制增强:

java 复制代码
protected <T> T doGetBean(...) {
    // 步骤1:名称转换(固定)
    // 步骤2:缓存查找(固定)
    // 步骤3:获取 Bean 定义(可扩展:合并机制)
    // 步骤4:检查依赖(可扩展:dependsOn)
    // 步骤5:创建 Bean(可扩展:不同作用域)
    // 步骤6:处理 FactoryBean(可扩展:特殊处理)
}

💡 关键理解:模板方法模式让 Spring 在保持核心流程不变的同时,提供了丰富的扩展点。

2. 缓存机制

Spring 使用了多级缓存来优化性能:

  • Bean 定义缓存:避免重复解析配置
  • 单例缓存:避免重复创建单例 Bean
  • 早期引用缓存:解决循环依赖问题

🔍 发现:缓存机制体现了 Spring 对性能的重视,通过缓存减少重复计算。

3. 延迟加载机制

Spring 采用了多种延迟加载策略:

  • Bean 定义延迟加载:只有在需要时才解析 Bean 定义
  • Bean 实例延迟加载 :只有在调用 getBean() 时才创建 Bean
  • 早期引用延迟创建:只有在真正需要时才通过 ObjectFactory 创建

💡 思考:延迟加载让 Spring 能够支持大型应用,避免启动时加载所有 Bean。

4. 扩展机制

Spring 提供了丰富的扩展点:

  • BeanPostProcessor:在 Bean 初始化前后进行处理
  • BeanFactoryPostProcessor:在 Bean 定义加载后进行处理
  • FactoryBean:自定义 Bean 创建逻辑
  • Aware 接口:让 Bean 感知容器信息

🔍 发现:扩展机制让 Spring 能够在不修改核心代码的情况下,支持各种功能扩展。

关键方法总结

方法 作用 设计模式 所在文章
doGetBean Bean 获取的核心方法 模板方法模式 (一)
transformedBeanName 名称转换 - (二)
canonicalName 别名解析 - (二)
getSingleton 从缓存获取单例 - (二)、(三)
getMergedBeanDefinition 获取合并后的 Bean 定义 - (五)
createBean 创建 Bean 实例 模板方法模式 (一)
doCreateBean Bean 创建的核心方法 模板方法模式 (一)、(三)
populateBean 属性注入 - (三)
initializeBean Bean 初始化 模板方法模式 (六)
invokeAwareMethods 调用 Aware 接口 策略模式 (六)
getObjectForBeanInstance 处理 FactoryBean 工厂方法模式 (四)

学习要点回顾

通过整个系列的学习,我们应该掌握以下要点:

1. 理解 Bean 获取的完整流程

从 getBean() 到 doGetBean(),再到 createBean() 和 doCreateBean(),理解每个方法的作用和调用关系。

2. 掌握核心机制

  • 名称转换:如何处理别名和 FactoryBean 前缀
  • 缓存机制:三级缓存的作用和关系
  • 循环依赖:如何通过三级缓存解决循环依赖
  • FactoryBean:如何区分获取工厂本身和工厂创建的对象
  • Bean 定义合并:如何处理父子 Bean 定义的继承
  • Aware 接口:如何通过回调机制让 Bean 感知容器信息

3. 理解设计思想

  • 模板方法模式:定义算法骨架,提供扩展点
  • 缓存机制:优化性能,减少重复计算
  • 延迟加载:支持大型应用,避免启动时加载所有 Bean
  • 扩展机制:提供丰富的扩展点,支持功能扩展

4. 掌握调试技巧

通过实际的调试过程,理解源码的执行流程,这是深入理解 Spring 的关键。

与后续学习的联系

💡 关键理解 :getBean() 是 Spring IoC 容器的核心方法,理解它有助于理解 Spring 的其他机制:

  1. Bean 生命周期 :getBean() 触发了 Bean 的完整生命周期
  2. 依赖注入 :在 populateBean() 中完成依赖注入
  3. AOP 代理:在 BeanPostProcessor 中创建 AOP 代理
  4. 事务管理:通过 AOP 代理实现事务管理
  5. Web 上下文:父子容器的机制在 Web 应用中广泛应用

🔍 发现 :Spring 的各个机制是相互关联的,理解 getBean() 是理解其他机制的基础。

总结

通过整个 getBean 源码实战系列的学习,我们深入理解了 Spring IoC 容器的核心机制。从最简单的 Bean 获取,到复杂的循环依赖处理,再到 FactoryBean、MergedBeanDefinition 和 BeanAware 机制,每一步都体现了 Spring 框架的设计智慧。

💡 核心收获:

  1. 理解了 Bean 获取的完整流程:从名称转换到 Bean 创建,再到初始化
  2. 掌握了核心机制:三级缓存、循环依赖、FactoryBean、Bean 定义合并、Aware 接口
  3. 理解了设计思想:模板方法模式、缓存机制、延迟加载、扩展机制
  4. 掌握了调试技巧:通过实际调试理解源码执行流程

🔍 下一步学习建议:

  • BeanPostProcessor:深入理解 Bean 后置处理器的机制
  • AOP 机制:理解 AOP 代理的创建过程
  • 事务管理:理解 Spring 事务管理的实现原理
  • Web 上下文:理解父子容器在 Web 应用中的应用

💡 最后的话:源码学习是一个循序渐进的过程,不要急于求成。通过实际调试、深入思考,才能真正理解 Spring 的设计思想。希望这个系列能够帮助大家更好地理解 Spring 框架!

相关推荐
独隅2 分钟前
Claude 高效使用指南深度评测
java·开发语言·claude·claude code
ByteCoder.9 分钟前
班前十分钟的清单,按体检引导单来写
java·服务器·前端·助播虾
海的透彻11 分钟前
如何让docker容器启动时用自定义的用户
java·docker·dubbo
疯狂学习GIS15 分钟前
将谷歌地球引擎账号升级为Contributor与Partner层级的方法
java·数据库·人工智能
GG.WinGel27 分钟前
质数数目(蓝桥杯练习题)
java·数据结构·算法·青少年编程·蓝桥杯
泡泡糖的中文规格书32 分钟前
HDLbits 2.2 学习笔记 · 多路复用器
java·开发语言
John_Hui34 分钟前
第 3 篇:配置 JDK 17 环境
java·开发语言·ubuntu
秋天的怀念_David39 分钟前
算法一千零一夜 · 第十七夜 长街点灯
java·程序人生·算法
不吃鱼的羊1 小时前
如何应对恐惧
java
不吃鱼的羊1 小时前
如何建立高自尊
java·服务器·前端