type
status
date
slug
summary
tags
category
icon
password

认识 @Configuration

Spring 在容器的初始化中使用注解 @Configuration 注解代替了 applicationContext.xml 文件,省略了之前在 xml 文件中配置 bean,配置 bean 扫描等过程。例如
上面是一个最简单的配置类,可以加载 MyBean 实例到 Spring 容器中。
查看 @Configuration 的源码
从定义来看, @Configuration 注解本质上还是 @Component,因此 <context:component-scan/> 或者 @ComponentScan 都能处理 @Configuration 注解的类。
@Configuration 标记的类必须符合下面的要求:
  • 配置类必须以类的形式提供(不能是工厂方法返回的实例),允许通过生成子类在运行时增强(cglib 动态代理)。
  • 配置类不能是 final 类(没法动态代理)。
  • 配置注解通常为了通过 @Bean 注解生成 Spring 容器管理的类,
  • 配置类必须是非本地的(即不能在方法中声明,不能是 private)。
  • 任何嵌套配置类都必须声明为static。
  • @Bean 方法可能不会反过来创建进一步的配置类(也就是返回的 bean 如果带有 @Configuration,也不会被特殊处理,只会作为普通的 bean)。
Spring 是如何使用 @Configuration 注解实现这些功能的呢?

BeanFactoryPostProcessor 是什么

想要了解配置类是什么时候生效的,首先要了解 BeanFactoryPostProcessor。我们看下 Spring 中 Bean 的生成过程。简单来讲,Spring 会先生成 BeanFactory(也就是 IOC 容器),再由 BeanFactory 生成 Bean。BeanFactory 是个 Factory,也就是 IOC 容器或对象工厂。它的职责包括:实例化、定位、配置 Bean 对象及建立这些对象间的依赖。在 Spring 中,所有的 Bean 都是由 BeanFactory 来进行管理的。
BeanFactory 首先会从 xml 文件或注解配置中读取 Bean 的各种信息,Spring 将这些信息称为 BeanDefinition。一般而言,我们通过 BeanDefinition 就可以直接生成 Bean 了。但是 Spring 饶了这么大一个圈子肯定不是为了简单的生成一个 Bean。Spring 会读取 BeanDefinition 的信息,区分出特殊的 Bean(实现 BeanFactoryPostProcessor接口的 Bean)。这些特殊的 Bean 会被优先处理。
BeanFactoryPostProcessor ---> 普通 Bean 构造方法
所以,BeanFactoryPostProcessor 用于扩展或是修改当前已经加载好的 BeanDefinition。这样就可以在真正初始化Bean 之前对 Bean 做一些处理操作,这也是 Spring 一个很重要的扩展点。

BeanDefinitionRegistryPostProcessor 是什么

Spring Boot 中大部分 BeanFactoryPostProcessor 是在初始化 context 实例的时候加入的,也有一部分是以 ApplicationContextInitializer 的形式加入容器中的,详见Spring Boot 扩展 ApplicationContextInitializer 的方式。简单来讲,SPring Boot 扩展了初始化器,这些初始化器会生成一些 BeanFactoryPostProcessor 的实现类,加入到容器中。 我们暂且不用关心到底是哪些 BeanFactoryPostProcessor。
BeanFactoryPostProcessor 有一个比较麻烦的地方,在于它可以随意的增加 BeanFactory 中的 BeanDefinition。假设我们现在有两个 Bean 以及两个 BeanFactoryPostProcessor,一个 BeanFactoryPostProcessor 是用来增加 Bean 的,一个 BeanFactoryPostProcessor 是为所有的 Bean 增加一个功能。那很明显,用来增加 Bean 的 BeanFactoryPostProcessor 应该优先执行,才能保证后一个 BeanFactoryPostProcessor 的功能可以覆盖所有的 Bean。为了实现这种效果,Spring 中设定了一个 BeanFactoryPostProcessor 的子接口 BeanDefinitionRegistryPostProcessor。
如果一个 BeanFactoryPostProcessor 是用来为容器增加 BeanDefinition 的,那就继承 BeanDefinitionRegistryPostProcessor。Spring 通过这种方式区分 BeanFactoryPostProcessor,也用来保证 BeanFactoryPostProcessor 的执行顺序。现在,我们的执行顺序变成了这样。
BeanDefinitionRegistryPostProcessor --> BeanFactoryPostProcessor --> 普通 Bean 构造方法
需要注意的是,BeanDefinitionRegistryPostProcessor 注册的 BeanDefinition 可能也是一个 BeanDefinitionRegistryPostProcessor,所以在注册结束后,还需要重新判断是否加入了新的 BeanDefinitionRegistryPostProcessor,如果是,那就要执行新加入的 BeanDefinitionRegistryPostProcessor 的注册方法,直到数量不再增加,才能认为所有的 BeanDefinition 都加载到了容器中。如下方法截取自 PostProcessorRegistrationDelegate 类 invokeBeanFactoryPostProcessors 方法。
invokeBeanDefinitionRegistryPostProcessors 最终执行 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法,完成 BeanDefinition 的注册。

ConfigurationClassPostProcessor 的工作流程

了解了 BeanFactoryPostProcessor 后,我们再回过头来看配置类的解析方式。Configuration 主要是用于向容器中注入新的 Bean 的,肯定是由某个 BeanDefinitionRegistryPostProcessor 处理。这个 BeanDefinitionRegistryPostProcessor 其实就是 ConfigurationClassPostProcessor,看名字也知道是用来处理配置类的,那 ConfigurationClassPostProcessor 是如何加入容器的呢?
Application 的启动流程参考 Spring源码:ApplicationContext启动流程。在 refresh() 第5步 invokeBeanFactoryPostProcessors(beanFactory); 会调用所有的 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry,其中一个 BeanDefinitionRegistryPostProcessor 就是 ConfigurationClassPostProcessor。
以 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry 方法为主线分析
进入 processConfigBeanDefinitions 方法,这里是核心方法。

checkConfigurationClassCandidate

checkConfigurationClassCandidate 方法用来判定类是否是一个配置类。这里有两个不同的标记 CONFIGURATION_CLASS_FULLCONFIGURATION_CLASS_LITE
  • CONFIGURATION_CLASS_FULL 很好理解,就是带有 @Configuration 注解的。
  • CONFIGURATION_CLASS_LITE :并不是只有 @Configuration 才能增加新的 BeanDefination,普通的用 @component 注解的类,如果其中的某个方法,使用 @Bean 注解,其实也能生成一个新的 BeanDefination,或者使用@ComponentScan,@Import,@ImportResource 注解的类也会生成新的类,这样类就用 CONFIGURATION_CLASS_LITE 标记,可以认为它也是一种配置类。

parse

进入核心方法 parse 方法
进入 processConfigurationClass 方法,使用递归的方法解析配置类,将配置类、@ComponmentScan扫描包下的bean、@Import引入的非DefferedImportedSelector类注册到IOC容器
进入 doProcessConfigurationClass 方法,这里会真正去处理各种注解
  • @PropertySource :加载指定的配置文件,如下就是加载 resources 文件夹下的 demo.properties 文件。将其作为一个配置源。
  • @ComponentScan :根据配置的扫描路径,把符合扫描规则的类装配到容器中。该注解默认会扫描该类所在的包下所有的配置类。
  • @Import :@Import 注解比较复杂,因为 Import 有三种使用方法,参考 Spirng系列:@Import注解。参考下面的代码解析。
  • @ImportResource :@ImportResource 的对象就是一个或多个配置文件路径。本质是读取 xml 文件,将配置文件中的 Bean 配置项加载到容器中。
  • @Bean :大家最常用的注解,之前的注解都是放在类上的,@Bean 是放在方法上的。
进入 processImports 方法,此处是读取 @Import 引入的类,如:Spring Boot 中的经典注解 ·@EnableAutoConfiguration· 注解通过 @Import 引入类 AutoConfigurationImportSelector,为自动化配置类的初始化做准备
进入 deferredImportSelectorHandler 方法,此处是对自动化配置类进行操作的入口。
进入 processGroupImports 方法。
 

总结

  • BeanFactoryPostProcessor 是 Spring 提供的修改 BeanDefinition 的扩展点,在 Bean 实例化前后执行
  • BeanDefinitionRegistryPostProcessor 是 BeanFactoryPostProcessor 的子接口,可以在 BeanFactoryPostProcessor 之前执行。BeanDefinitionRegistryPostProcessor 是 ConfigurationClassPostProcessor 类的基础。
  • ConfigurationClassPostProcessor 类负责解析配置类。主要处理@Configuration、@ComponentScan、@Import、@ImportResource、@Bean等注解。
    • 对于 @Configuration 注解,它会判断是否是一个配置类,并设置为lite或full,这取决于proxyBeanMethods属性的值。
    • 对于 @ComponentScan注解,它会扫描指定路径下的类,并将符合条件的类装配到容器中。
    • 对于 @Import 注解,它会处理引入的类,特别是处理DeferredImportSelector接口的实例,这在Spring Boot的自动化配置中尤其重要。
    • 对于 @ImportResource 注解,它会加载指定的配置文件。
    • 对于 @Bean 注解,它会处理加了 @Bean 的注解方法。

参考

Spring系列:AOP使用Spring系列:@Import注解
mcbilla
mcbilla
一个普通的干饭人🍚
Announcement
type
status
date
slug
summary
tags
category
icon
password
🎉欢迎来到飙戈的博客🎉
-- 感谢您的支持 ---
👏欢迎学习交流👏