-
死磕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 相关文章