【源码】Spring —— @Configuration 1 ConfigurationClassPostProcessor
前言
被 @Configuration
注解注释的类,我们一般称之为 配置类
,实际上,同样被 @Import
@ComponentScan
等注解注释的类,甚至一个含有 @Bean
注解注释方法的 普通类
也算是一个 配置类
,官方以 FULL
和 LITE
区分它们
FULL 配置类
:Spring
会为我们生成它的代理类,代为管理bean
方法调用的依赖关系。换句话说,在该配置类中调用其他@Bean
注解注释的方法,会转而去容器
中获取对应的bean
实例,而不是再创建一次LITE 配置类
:即普通的配置类,Spring
不会代理它,进而换取的是性能开销上的节省
版本
springframework:5.2.x
@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
// 配置类对应的 beanName
@AliasFor(annotation = Component.class)
String value() default "";
// 是否代理 bean 方法,默认 true,false 时该配置类相当于 Lite Mode
// @since 5.2
boolean proxyBeanMethods() default true;
}
其中,proxyBeanMethods
属性自 5.2
后提供,默认 true
,false
时不对该配置类进行代理,即 LITE MODE
ConfigurationClassPostProcessor
ConfigurationClassPostProcessor
是 Spring
提供的 唯一的BeanDefinitionRegistryPostProcessor
,它的调用发生在 AbstractApplicationContext#refresh
方法的第五步 invokeBeanFactoryPostProcessors
方法中,该方法的部分逻辑不完全地概括如下:
- 执行所有容器中
BeanDefinitionRegistryPostProcessor
的postProcessBeanDefinitionRegistry
方法 - 执行所有容器中
BeanDefinitionRegistryPostProcessor
的postProcessBeanFactory
方法 - 执行所有容器中
BeanFactoryPostProcessor
的postProcessBeanFactory
方法
关于更多细节,可以阅读下面的文章
【Spring】ApplicationContext 二 AbstractApplicationContext
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
// 已解析
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
// 开始解析
processConfigBeanDefinitions(registry);
}
解析发生在 processConfigBeanDefinitions
方法
ConfigurationClassPostProcessor#processConfigBeanDefinitions
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
/**
* 获取所有的 BeanDefinitionName 作为备选
* 这个地方,一般就是 spring 内部注册的一些 BeanDefinition
* 和我们的 主(配置)类
*/
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// 已经进行过解析
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
// ...
}
// 如果是配置类,则加入备选,无论 FULL or LITE
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 没有需要解析的配置类
if (configCandidates.isEmpty()) {
return;
}
// 排序
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// 略
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 解析配置类
parser.parse(candidates);
/**
* 校验
* 如果是 FULL 配置类
* 则类不能为 final,方法需要被复写
*/
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
// 移除已经解析过的类
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// 加载对应的 BeanDefinition
/**
* configClasses 中的 BeanDefinition
* 由 ConfigurationClassBeanDefinitionReader 解析
*/
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
/**
* registry.getBeanDefinitionCount() > candidateNames.length
* 意味着容器中又注册了新的 BeanDefinition(一般不会)
* 我们需要拿出新注册的这些(可能是配置类),再进行解析
*/
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
/**
* 新注册的 BeanDefinition 是配置类,且之前没解析过
* 则继续解析,直到 candidates 为空
*/
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
// 略
}
因为方法篇幅过长,略去相对无关的代码,则主要逻辑概括为:
- 从目前为止容器中注册的配置类中筛选配置类,在 SpringBoot 环境中,这里一般就是主类
- 过滤备选配置类,主要委托给了
ConfigurationClassUtils#checkConfigurationClassCandidate
方法,核心逻辑如下:
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
// 有 Configuration 注解且 proxyBeanMethods 属性为 true,即 FULL
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
/**
* 1)不是接口
* 2)标注有 Component ComponentScan Import ImportResource 注解
* 3)还有标注了 Bean 注解的方法
* 则为 LITE
*/
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
// 否则不是配置类
else {
return false;
}
- 交给
ConfigurationClassParser
类解析配置类,解析过程可能会注册必要的BeanDefinition
,同时所有的解析类会被封装到parser
中,对于解析的细节我们在下文详解 - 解析完后进行必要的校验
- 由
ConfigurationClassBeanDefinitionReader
注册所有配置类中对应的BeanDefinition
,比如@Import
、@Bean
等注解的处理 - 如果注册的
BeanDefinition
里又有新的配置类
,继续解析,但一般情况这里不会再有新增的配置类了
ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
刚才提到,在 ConfigurationClassParser
解析完 配置类
后,由 ConfigurationClassBeanDefinitionReader
注册其中的 BeanDefinition
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
/**
* 遍历所有 ConfigurationClass 进行处理
*/
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
------ loadBeanDefinitionsForConfigurationClass ------
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
/**
* 对于被 Import 的类:
* 1)比如普通成员内部配置类
* 2)被 @Import 的类本身
* 注册对应的 BeanDefinition
*/
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
/**
* 处理所有 @Bean 对应的方法
* 注册对应的 ConfigurationClassBeanDefinition
*/
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 处理 @ImportResource
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 执行 ImportBeanDefinitionRegistrar#registerBeanDefinitions
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
解析出来的 BeanDefinition
是在这里按顺序注册的:
Imported
类的注册,这里涉及的场景比如:- 配置类的普通成员内部类(
Inner Class
) @Import
注解引入的类本身
- 配置类的普通成员内部类(
@Bean
方法以factoryMethod
形式注册的Bean
组件@ImportResource
对应的BeanDefinition
ImportBeanDefinitionRegistrar#registerBeanDefinitions
的执行
总结
可以看到,配置类的解析最终是交由 ConfigurationClassParser
来处理的,该过程发生在 容器启动的很早期
,此时无论 BeanPostProcessor
还是自定义的 Bean
实例都没有初始化
下一篇:【源码】Spring —— @Configuration 2 ConfigurationClassParser 、Static 关键字提高配置类优先级