【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 前缀&beanNamebeanName
  2. 处理别名转换aliascanonicalName(支持链式别名)

设计思想

  • 统一入口:所有名称转换都通过这个方法,保证一致性
  • 链式别名支持:别名可以指向另一个别名,最终指向 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 框架!

相关推荐
+VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
一起努力啊~3 小时前
算法刷题-二分查找
java·数据结构·算法
小途软件3 小时前
高校宿舍访客预约管理平台开发
java·人工智能·pytorch·python·深度学习·语言模型
J_liaty3 小时前
Java版本演进:从JDK 8到JDK 21的特性革命与对比分析
java·开发语言·jdk
code bean4 小时前
Flask图片服务在不同网络接口下的路径解析问题及解决方案
后端·python·flask
+VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
努力的小郑4 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
daidaidaiyu4 小时前
一文学习和实践 当下互联网安全的基石 - TLS 和 SSL
java·netty
hssfscv4 小时前
Javaweb学习笔记——后端实战2_部门管理
java·笔记·学习
NE_STOP4 小时前
认识shiro
java