死磕Spring之IoC篇 - @Bean 等注解的实现原理
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读
Spring 版本:5.1.14.RELEASE
开始阅读这一系列文章之前,建议先查看《深入了解 Spring IoC(面试题)》这一篇文章
该系列其他文章请查看:《死磕 Spring 之 IoC 篇 - 文章导读》
@Bean 等注解的实现原理
通过前面的一系列文章我们了解到 @Component
注解(及其派生注解)标注的 Class 类都会被解析成 BeanDefinition(Bean 的“前身”),然后会被初始化成 Bean 对象。那么 @Bean
注解不是 @Component
的派生注解,且用于标注方法,该注解的解析过程在前面的文章中没有提到,那么在 Spring 中是如何解析 @Bean
注解的呢?
可以先回顾一下《Spring 应用上下文 ApplicationContext》文章“BeanFactory 后置处理阶段”的小节中讲到,在创建好 BeanFactory 后会调用所有的 BeanFactoryPostProcessor 处理器对其进行后置处理。@Bean
注解就是在这个过程被解析的,解析过程大致就是遍历所有的 BeanDefinition,如果其内部包含 @Bean
标注的注解,则会将该方法解析出一个 BeanDefinition 对象并注册。当然,除了 @Bean
注解外,例如 @ComponentScan
、@Import
、@ImportResource
、@PropertySource
注解都是在该过程中进行解析的。那么接下来将分析整个的解析过程,入口在 ConfigurationClassPostProcessor
这个处理器中。
概览
主要涉及以下几个类:
org.springframework.context.annotation.ConfigurationClassPostProcessor
,处理 Spring 应用上下文中的配置类,解析@Bean
等注解,并进行 CGLIB 提升org.springframework.context.annotation.ConfigurationClass
,根据前面提到的配置类解析出来的对象,包含各种注解的信息,例如@Bean
、@Import
org.springframework.context.annotation.ConfigurationClassParser
,解析配置类,生成 ConfigurationClass 对象并保存org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
,配置类中 BeanDefinition 的读取器,根据 ConfigurationClass 解析出 BeanDefinition 并注册
配置类:带有
@Configuration
注解的类,如果这个类带有@Component
|@ComponentScan
|@Import
|@ImportSource
注解,或者内部存在@Bean
的方法都算配置类
几个关键处理器的注册
处理器
关键的处理器:
ConfigurationClassPostProcessor
:处理 Spring 应用上下文中的配置类,解析@Bean
等注解,并进行 CGLIB 提升AutowiredAnnotationBeanPostProcessor
:解析@Autowired
和@Value
注解标注的属性,获取对应属性值,进行依赖注入CommonAnnotationBeanPostProcessor
:会解析@Resource
注解标注的属性,获取对应的属性值,进行依赖注入EventListenerMethodProcessor
:解析@EventListener
注解标注的方法,会将其解析成 Spring 事件监听器并注册DefaultEventListenerFactory
:帮助 EventListenerMethodProcessor 将@EventListener
注解标注的方法生成事件监听器
注册过程
在《BeanDefinition 的解析过程(面向注解)》文章中讲到,如果在 XML 配置文件中配置了 <context:component-scan />
标签,会通过 ClassPathBeanDefinitionScanner 扫描器进行解析;在《Spring 应用上下文 ApplicationContext》文章的“BeanFactory 创建阶段”小节中讲到,支持注解配置的 Spring 应用上下文会通过 ClassPathBeanDefinitionScanner 扫描器进行扫描。
在 ClassPathBeanDefinitionScanner 的扫描过程中会调用 AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry) 方法,如下:
我们再来看到 AnnotationConfigUtils 的这个方法,如下:
整个过程就是将上面“处理器”小节中讲到的几个处理器进行注册
ConfigurationClassPostProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor
,BeanDefinitionRegistryPostProcessor 处理器,解析配置类
构造方法
实现了 BeanDefinitionRegistryPostProcessor 接口(继承了 BeanFactoryPostProcessor 接口)
在《Spring 应用上下文 ApplicationContext》文章“BeanFactory 后置处理阶段”的小节可以知道,BeanDefinitionRegistryPostProcessor 优先于 BeanFactoryPostProcessor,所以我们先来看到前者的实现
1. postProcessBeanDefinitionRegistry 方法
postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
方法,对 BeanDefinitionRegistry 的后置处理,其实这个入参就是 DefaultListableBeanFactory,如下:
使用 registry
的哈希值作为 ID 保存在 registriesPostProcessed
,保证同一个 BeanDefinitionRegistry 不会被重复处理,最后调用 processConfigBeanDefinitions(BeanDefinitionRegistry)
方法
2. processConfigBeanDefinitions 方法
processConfigBeanDefinitions(BeanDefinitionRegistry)
方法,处理配置类(过程有点长),如下:
过程如下:
- 获取所有的 BeanDefinition 名称的集合
- 找出是配置类的 BeanDefinition 对象们,保存至
configCandidates
集合中- 判断是否已经处理过,已处理则不再处理,保证不被二次处理,否则
- 检查是否带有
@Configuration
注解,或者带有@Component
|@ComponentScan
|@Import
|@ImportSource
| 内部存在@Bean
的方法,符合前面其中一个条件都算配置类,需要进行处理
- 上一步没有找到需要处理的配置类,则直接
return
返回 - 根据 @Order 注解对
configCandidates
集合中的配置类进行排序
- 创建一个
ConfigurationClassParser
对象,用于解析符合条件的配置类,会先生成 ConfigurationClass 对象保存至其内部,然后通过ConfigurationClassBeanDefinitionReader
读取器从这些 ConfigurationClass 对象中解析出 BeanDefinition- 【核心】对所有的配置类进行解析,调用
ConfigurationClassParser#parse(Set<BeanDefinitionHolder>)
方法,解析其内部的注解(@PropertySource
、@ComponentScan
、@Import
、@ImportResource
、@Bean
)。每个配置类会生成一个ConfigurationClass
对象,其中@Bean
标注的方法转换成 BeanMethod 对象保存在 ConfigurationClass.beanMethods 集合中 - 对所有的 ConfigurationClass 对象进行校验:Class 对象不能被 final 修饰,
@Bean
标注的方法不能被 private 修饰 - 获取上面解析出来的 ConfigurationClass 们,放入
configClasses
集合中,并移除已经处理过的对象 - 创建一个
ConfigurationClassBeanDefinitionReader
对象,调用其loadBeanDefinitions(Set<ConfigurationClass>)
方法,扫描出 ConfigurationClass 中的 BeanDefinition 并注册。例如@Bean
标注的方法需要注册、@ImportResource
注解配置的资源文件中配置的 Bean 需要注册- 【核心】扫描所有的 ConfigurationClass,注册相应的 BeanDefinition,主要有以下来源:
@Import
注解导入对象- 其内部定义的带有
@Bean
注解的方法 @ImportResource
注解导入资源@Import
注解导入的 ImportBeanDefinitionRegistrar 接口的实现类可自定义实现注册相关 BeanDefinition
- 【核心】扫描所有的 ConfigurationClass,注册相应的 BeanDefinition,主要有以下来源:
- 将这些 ConfigurationClass 保存至
alreadyParsed
已解析的集合中
- 【核心】对所有的配置类进行解析,调用
- 从上述过程注册的 BeanDefinition 中,找到没有还解析过的 BeanDefinition 们,再循环解析。例如
@Bean
标注的方法是新注册的 BeanDefinition,也可能又是一个配置类,但是还没有被这里解析过,所以需要再次扫描,如果还有未处理的配置类则需要进行处理 - 清理上述解析过程中产生的元数据缓存,例如通过 ASM 从 .class 文件中获取到的 Class 信息,需要清理
总结:先根据配置类生成 ConfigurationClass 对象,然后根据该对象解析出 BeanDefinition 并注册
配置类:带有
@Configuration
注解的类,如果这个类带有@Component
|@ComponentScan
|@Import
|@ImportSource
注解,或者内部存在@Bean
的方法都算配置类
上面的第 5.1
和 5.4
分别对应 ConfigurationClassParser 和 ConfigurationClassBeanDefinitionReader 两个类,接下来会依次分析
3. postProcessBeanFactory 方法
postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
方法,对 ConfigurableListableBeanFactory 的后置处理,其实这个入参就是 DefaultListableBeanFactory,和 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)
方法是同一个入参,如下:
如果这个 DefaultListableBeanFactory 没有处理过,这里会和上面的过程一样调用 processConfigBeanDefinitions(BeanDefinitionRegistry)
方法进行处理。接下来,会调用 enhanceConfigurationClasses(ConfigurableListableBeanFactory)
方法对 @Configuration
注解的配置类进行 CGLIB 提升,主要帮助实现 AOP 特性
4. enhanceConfigurationClasses 方法
enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory)
方法,对 @Configuration
注解的配置类进行 CGLIB 提升,如下:
整个过程大致就是:如果是 @Configuration
注解标注的类,则通过 CGLIB 创建一个子类(代理类)并设置到这个 BeanDefinition 的 beanClass
属性中。这样一来, @Configuration
注解标注的类就得到了 CGLIB 的提升,主要帮助实现 AOP 相关特性,这里不做详细展述,具体过程请期待后续的 Spring AOP 相关文章😄
ConfigurationClass
org.springframework.context.annotation.ConfigurationClass
,根据前面提到的配置类解析出来的对象,如下:
重写了 equals
方法,是同一个 Class 对象也是相等的
ConfigurationClassParser
org.springframework.context.annotation.ConfigurationClassParser
,解析配置类,生成 ConfigurationClass 对象并保存
构造方法
1. parse 方法
parse(Set<BeanDefinitionHolder> configCandidates)
方法,对这些配置类进行解析,如下:
遍历这些配置类,调用 parse(...)
不同的重载方法进行解析,如下:
先为这个 配置类 创建一个 ConfigurationClass 对象,然后再调用 processConfigurationClass(ConfigurationClass)
方法进行解析
2. processConfigurationClass 方法
processConfigurationClass(ConfigurationClass configClass)
方法,解析配置类,所有信息都保存在这个 ConfigurationClass 中,如下:
过程如下:
- 会先根据
@Conditional
注解判断是否需要跳过 - 调用
doProcessConfigurationClass(ConfigurationClass, SourceClass)
方法进行解析,会遍历父类 - 将配置类对应的 ConfigurationClass 对象保存至
configurationClasses
集合中
3. doProcessConfigurationClass 方法
doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
方法,解析配置类,所有信息都保存在这个 ConfigurationClass 中,如下:
过程大致如下:
- 先处理内部成员类,调用
processMemberClasses(...)
方法,如果内部成员类也是配置类,同样调用前面的processConfigurationClass(ConfigurationClass)
方法进行处理,这里不展开讲述 - 处理
@PropertySource
注解,加载出对应的 Resource 资源,将其添加至 Environment 环境中,这里不展开讲述 - 处理
@ComponentScan
注解,通过 ComponentScanAnnotationParser 扫描出指定包路径下的 BeanDefinition 们并注册,然后再遍历处理,如果是配置类,同样调用前面的parse(...)
方法,这里不展开讲述 - 处理
@Import
注解中的 Class 对象,调用processImports(...)
方法,这里不展开讲述,分为下面几种情况:- ImportSelector 的实现类:调用其
selectImports(AnnotationMetadata)
方法获取需要导入的 Class 类名,再次调用processImports(...)
方法进行处理 - ImportBeanDefinitionRegistrar 的实现类:将这个实现类保存在
ConfigurationClass.importBeanDefinitionRegistrars
集合中 - 否则,为
@Import
注解中的 配置类 创建 ConfigurationClass 对象,同样调用processConfigurationClass(ConfigurationClass)
方法进行处理
- ImportSelector 的实现类:调用其
- 处理
@ImportResource
注解,获取需要导入的资源配置信息,将这些配置信息添加至ConfigurationClass.importedResources
集合中 - 解析出所有带有
@Bean
注解的方法,底层通过 ASM(Java 字节码操作和分析框架)进行解析,然后将这些方法封装成 BeanMethod 对象,并保存至ConfigurationClass.beanMethods
集合中,这里不展开讲述 - 如果有父类,则循环进行解析
整个过程就是解析配置类中的各种注解,解析结果都保存在 ConfigurationClass 中,所以说整个过程就是为配置类生成一个 ConfigurationClass 对象,将这些信息生成对应的 BeanDefinition 对象并注册到 Spring 上下文的过程还在后面,也就是下面要讲的 ConfigurationClassBeanDefinitionReader 读取器
ConfigurationClassBeanDefinitionReader
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
,配置类中 BeanDefinition 的读取器,根据 ConfigurationClass 解析出 BeanDefinition 并注册
1. loadBeanDefinitions 方法
loadBeanDefinitions(Set<ConfigurationClass>)
方法,从 ConfigurationClass 中加载出 BeanDefinition,如下:
遍历所有的 ConfigurationClass 对象,调用 loadBeanDefinitionsForConfigurationClass(...)
方法
2. loadBeanDefinitionsForConfigurationClass 方法
loadBeanDefinitionsForConfigurationClass(ConfigurationClass, TrackedConditionEvaluator)
方法,从 ConfigurationClass 中加载出 BeanDefinition 并注册,如下:
过程大致如下:
-
如果不符合
@Conditional
注解的条件,则跳过 -
如果当前 ConfigurationClass 是通过
@Import
注解被导入的-
根据该 ConfigurationClass 对象生成一个 BeanDefinition 并注册,调用
registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass)
方法,如下:根据这个类的元信息生成一个 AnnotatedGenericBeanDefinition 对象,并注册
-
-
遍历当前 ConfigurationClass 中所有的
@Bean
注解标注的方法- 根据该 BeanMethod 对象生成一个 BeanDefinition 并注册(注意这里有无 static 修饰会有不同的配置),调用
loadBeanDefinitionsForBeanMethod(BeanMethod)
方法,在后面分析
- 根据该 BeanMethod 对象生成一个 BeanDefinition 并注册(注意这里有无 static 修饰会有不同的配置),调用
-
对
@ImportResource
注解配置的资源进行处理,对里面的配置进行解析并注册 BeanDefinition,通过 XmlBeanDefinitionReader 对该配置文件进行扫描,在前面的《BeanDefinition 的加载阶段(XML 文件)》文章中已经分析过 -
通过
@Import
注解导入的 ImportBeanDefinitionRegistrar 实现类往 BeanDefinitionRegistry 注册 BeanDefinition,也就是调用这个实现类的registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)
方法,自定义注册 BeanDefinition
该过程会把 ConfigurationClass 中的信息解析成 BeanDefinition 并注册,其中第 5
步可参考 Mybatis 集成 Spring 的项目中的 MapperScannerRegistrar 类,可参考我的另一篇文章《精尽MyBatis源码分析 - MyBatis-Spring 源码分析》
3. loadBeanDefinitionsForBeanMethod 方法
loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod)
方法,将 @Bean
注解标注的方法解析成 BeanDefinition 并注册,如下:
整个过程并不复杂,大致如下:
- 如果不符合
@Conditional
注解的条件,则跳过 - 获取
@Bean
注解元信息 - 为
@Bean
标注的方法(被封装成了 BeanMethod 对象)创建一个 ConfigurationClassBeanDefinition 对象 - 根据注解元信息设置各种配置,例如
autowire
(注入模式)、initMethod
(初始化方法)、destroyMethod
(销毁方法),对于static
修饰的方法有区别,如下:- 静态方法会设置
beanClass
(Class 对象)和factoryMethodName
(方法名称),可以直接调用 Class 对象的这个方法获取 Bean - 非静态方法需要设置
factoryBeanName
(该方法所属 Bean 的名称)和factoryMethodName
(方法名称),需要先初始化这个所属 Bean,才能调用这个方法获取 Bean
- 静态方法会设置
- 注册这个 ConfigurationClassBeanDefinition 对象
总结
在 《Spring 应用上下文 ApplicationContext》中有一个“BeanFactory 后置处理阶段”,在创建好 DefaultListableBeanFactory 后会调用所有的 BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessor 对其进行处理,前者优先。
Spring 应用上下文中的 ConfigurationClassPostProcessor
就是一个 BeanDefinitionRegistryPostProcessor 处理器,它会对所有的配置类(包括其内部成员类)进行处理,会做以下事情:
- 加载
@PropertySource
注解配置的资源到 Environment 环境中 - 扫描
@ComponentScan
注解指定路径下的 BeanDefinition 们,如果也是配置类,会进行同样的处理过程 - 解析出
@Import
注解配置的配置类,解析成 AnnotatedGenericBeanDefinition 并注册;其中配置的是 ImportSelector 实现类,则调用其selectImports(AnnotationMetadata)
方法获取配置类;如果配置的是 ImportBeanDefinitionRegistrar 实现类,则调用其registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)
方法,自定义注册 BeanDefinition - 解析
@ImportResource
注解,加载配置的 XML 文件,解析出 BeanDefinition 们 - 将这些配置类中
@Bean
注解的方法解析成 ConfigurationClassBeanDefinition 并注册
配置类:带有
@Configuration
注解的类,如果这个类带有@Component
|@ComponentScan
|@Import
|@ImportSource
注解,或者内部存在@Bean
的方法都算配置类
除了上面这些处理,如果是 @Configuration
注解标注的类,还会进行 CGLIB 提升,主要帮助实现 AOP 相关特性,这里没有详细展述,具体过程请期待后续的 Spring AOP 相关文章😄
至此,Spring IoC 的相关文章已全部完成,希望这一系列文章可以让读者对 Spring 有更加全面的认识,如有错误或者疑惑的地方,欢迎指正!!!共勉 👨🎓
__EOF__

本文链接:https://www.cnblogs.com/lifullmoon/p/14461712.html
关于博主:本着学习与分享的目的,将持续不断的进行知识分享。望各位到访看客如有喜欢的文章,可以点击一下“推荐”,若有不同建议或者意见,也请不吝赐教,博主感激不尽。另外,欢迎转载博主的文章,请务必依据文章下方的版权声明转载。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义