SpringFramework源码解析——@Configuration及其周边注解是如何生效的?
1. 背景
1.1. 如何将一个Bean注册到IoC容器中?
如何将一个Bean注册到IoC容器中?
- 在Spring2.5之前的版本,可以通过XML配置的方式,也就是通过
bean
标签来配置Bean。 - 在Spring2.5及之后的版本,可以通过注解扫描的方式,也就是在类上标注
@Component
或其派生注解。 - 在Spring3.0及之后的版本,可以通过配置类的方式,也就是在类上标注
@Configuration
注解。
使用配置类注册Bean有以下几种方式:
- 通过在配置类上标注
@ComponentScan
注解使用组件扫描的方式。 - 通过在配置类上标注
@Import
注解导入配置类,导入ImportSelector
或导入ImportBeanDefinitionRegistrar
的方式。 - 通过在配置类上标注
@ImportResource
注解导入资源的方式。 - 通过在配置类的方法上标注
@Bean
注解声明Bean的方式。
1.2. 为什么要有配置类?
从基于XML的配置过渡到基于Java代码的配置,以提供更好的可读性和灵活性。
1.3. 如何描述一个配置类?
描述一个配置类也就是描述配置类携带的信息,配置类包含以下信息:
- 配置类所标注的注解。
- 配置类的Bean名称。
- 配置类是否是被其他配置类导入的。
- 配置类声明的Bean方法。
- 配置类声明的导入资源。
- 配置类声明的
ImportBeanDefinitionRegistrar
。
Spring通过ConfigurationClass
类来描述配置类。ConfigurationClass
的类图如下:
2. 配置类是如何工作的?
配置类本质上也是一个Bean,不过通过注解提供了额外的配置信息。为了实现配置类的相关功能,Spring实现了ConfigurationClassPostProcessor
后置处理器,其是BeanDefinitionRegistryPostProcessor
接口的实现类,在Spring应用上下文刷新时会进行回调。配置类后置处理器的处理流程如下:
- 获取候选的配置类列表;
- 依次解析每个配置类;
- 依次从每个配置类中加载Bean定义。
2.1. 获取候选的配置类列表
判断当前Bean定义是否是配置类,如果是配置类则加入候选的配置类列表。Bean定义满足以下条件之一便是配置类:
- Bean定义对应的类标注了
@Configuration
注解; - Bean定义对应的类标注了
@Component
,或@ComponentScan
,或@Import
,或@ImportResource
注解; - Bean定义对应的类的某些方法标注了
@Bean
注解; - Bean定义的
org.springframework.context.annotation.ConfigurationClassPostProcessor.candidate
属性的属性值是true
,即显式注册的Bean。
对应源码所在位置:
ConfigurationClassUtils#checkConfigurationClassCandidate
。
2.2. 依次解析每个配置类
2.2.1. 评估条件
根据当前配置类所标注的注解信息和条件评估器ConditionEvaluator
来决定是否跳过该配置类的解析。
对应源码所在位置:
ConditionEvaluator#shouldSkip
。
2.2.2. 处理内部类
如果当前配置类中声明的内部类也是配置类,则对内部类进行配置类的解析。
对应源码所在位置:
ConfigurationClassParser#processMemberClasses
。
2.2.3. 处理@PropertySource注解
如果当前配置类标注了@PropertySouce
注解,则为Spring应用上下文中的环境添加配置源。
对应源码所在位置:
PropertySourceRegistry#processPropertySource
。
2.2.4. 处理@ComponentScan注解
如果当前配置类标注了@ComponentScan
注解,则对指定的包名进行组件扫描。如果扫描到的组件是配置类,则进行配置类的解析。
对应源码所在位置:
ComponentScanAnnotationParser#parse
。
2.2.5. 处理@Import注解
如果当前配置类标注了@Import
注解,有以下三种情况:
- 当导入的是
ImportSelector
时,则获取要导入的类名,进行递归导入处理; - 当导入的是
ImportBeanDefinitionRegistrar
时,则由ConfigurationClass
收集,供后续Bean定义的加载; - 当导入的是其他类时,则当作配置类进行处理,进行配置类的解析。
对应源码所在位置:
ConfigurationClassParser#processImports
。
2.2.6. 处理@ImportResource注解
如果当前配置类标注了@ImportResource
注解,则由ConfigurationClass
收集,供后续Bean定义的加载。
对应源码所在位置:
ConfigurationClass#addImportedResource
。
2.2.7. 处理@Bean注解
如果当前配置类的某些方法标注了@Bean
注解,则由ConfigurationClass
收集,供后续Bean定义的加载。
对应源码所在位置:
ConfigurationClassParser#retrieveBeanMethodMetadata
。
2.3. 依次从每个配置类中加载Bean定义
2.3.1. 注册被@Import导入的配置类
如果当前配置类是被其他配置类导入的,则进行Bean定义的注册。
对应源码所在位置:
ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass
。
2.3.2. 获取Bean方法并加载Bean定义
从配置类ConfigurationClass
中获取Bean方法,并进行Bean定义的加载。
对应源码所在位置:
ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod
。
2.3.3. 获取导入资源并加载Bean定义
从配置类ConfigurationClass
中获取导入资源,并进行Bean定义的加载。
对应源码所在位置:
ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromImportedResources
。
2.3.4. 获取ImportBeanDefinitionRegistrar并加载Bean定义
从配置类ConfigurationClass
中获取ImportBeanDefinitionRegistrar
,并进行Bean定义的加载。
对应源码所在位置:
ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
。