Spring 源码阅读 37:postProcessBeanDefinitionRegistry 对 @Configuration 配置的解析和处理(2)
概述
这一篇接着上篇,继续分析 ConfigurationClassPostProcessor 中postProcessBeanDefinitionRegistry
方法对标记了@``Configuration
注解的类的解析和处理。
建议两篇一起阅读。
处理过程
下图是postProcessBeanDefinitionRegistry
方法中执行具体解析过程的processConfigBeanDefinitions
方法的源代码。
上一篇分析了前半部分,也就是do-while
循环之前的部分。Spring 通过对所有注册到容器中的 BeanDefinition 进行筛选,找到了所有符合候选条件的配置类对应的 BeanDefinition,并且进行了一些准备性的处理。
接下来在do-while
循环中,对这些符合候选条件的 BeanDefinition,也就是candidates
集合中的元素,进行处理。下面开始。
配置类的解析
进入do-while
语句块之后,第一步就是使用实现创建好的配置类解析器对candidates
集合进行解析。
parser.parse(candidates);
我们进入这个方法查询解析的过程。
// org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
this.deferredImportSelectorHandler.process();
}
针对candidates
集合中的每一个 BeanDefinition,这里会根据它具体的类型,以 BeanDefinition 的元数据和 Bean 名称为参数,执行对应的parse
重载方法,因为我们要处理的 BeanDefinition 是基于注解生成的,因此,我们进入第一个if
分支中的parse
方法查看。
// org.springframework.context.annotation.ConfigurationClassParser#parse(org.springframework.core.type.AnnotationMetadata, java.lang.String)
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
这里将元数据和 Bean 名称封装成了一个ConfigurationClass 对象作为参数,调用了processConfigurationClass
方法。
// org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
方法中的第一个if
语句是根据配置类的@Conditional
注解,来判断是否跳过当前配置类的解析,这里的逻辑不在本文的讨论范畴之内,我们假设不跳过,接着分析后续的逻辑。
接下来,会在this.configurationClasses
查找当前的配置类,并赋值给existingClass
变量,当第一次解析配置类的时候,此时的existingClass
一定是空的,因此第二个if
语句块中的步骤不会被执行。
在两个if语句块之后,会递归地通过doProcessConfigurationClass
方法解析配置类及其继承的父类。解析完成之后的配置类对象,会被添加到this.configurationClasses
容器中。
因此,我们接下来重点看一下doProcessConfigurationClass
方法。
这个方法也比较长,其核心逻辑就是对当前配置类的配置信息进行解析和处理,包括递归处理内部类的配置信息、@PropertySource
注解的配置信息、@ComponentScan
注解的配置信息、@Import
注解的配置信息、@ImportResource
注解的配置信息、被@Bean
注解标记的方法,并且,如果包含父类的话,还会返回父类的信息供递归处理。
以上所有被解析的信息,都会作为 ConfigurationClass 对象的内容,保存到解析器的configurationClasses
集合中。
回到最初的processConfigBeanDefinitions
方法中,当parse
方法执行完之后,解析器还会通过validate
方法对解析的结果进行验证,确保解析到的都是符合 Spring 规定的配置。
parser.validate();
随后,还会从解析器中获取所有解析得到的 ConfigurationClass,并排除掉alreadyParsed
集合中的元素,避免后续重复处理。
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
至此,配置类的解析工作就完成了,解析的结果就是所有已经被处理的配置内容,和一个 ConfigurationClass 结合configClasses
。
@Bean
方法的解析
接着看后面的代码。
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
首先,创建了一个 ConfigurationClassBeanDefinitionReader 类型的reader
对象,然后通过调用它的loadBeanDefinitions
方法,加载configClasses
中的 BeanDefinition。之后,将configClasses
中所有的元素添加到alreadyParsed
中,代表alreadyParsed
中所有的配置类都解析完成了。
虽然在容器进行包路径扫描的时候,所有的配置类都创建了相应的 BeanDefinition,但是配置类中可以通过为一个方法添加@``Bean
注解来配置 Bean 的信息,因此,这一部分的loadBeanDefinitions
方法是为了处理配置类中被标记了@``Bean
注解的方法。
进入loadBeanDefinitions
方法查看源码。
// org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
这里,对集合中的每一个 ConfigurationClass 调用了loadBeanDefinitionsForConfigurationClass
方法。这里的 TrackedConditionEvaluator 是用来通过@Conditional
注解来判断是不是要跳过当前的配置类处理。
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;
}
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
如果当前的配置类是由别的配置类引入的,或者引入了别的配置类,都会有相应的处理。我们此时重点看方法体中for
循环的部分,在这里,会通过配置类的getBeanMethods
方法,将配置类中所有的 BeanMethod 获取到,并对其执行loadBeanDefinitionsForBeanMethod
方法。
从前面对parse方法的解析中可以看到,此处获取到的 BeanMethod 就是当前配置类中所有被标记了@``Bean
注解的方法集合。我们进入loadBeanDefinitionsForBeanMethod
方法中查看源码。
这个方法的代码量也非常可观。但是大部分逻辑都与之前分析过的 BeanDefinition 加载逻辑类似。首先,会读取@``Bean
注解的信息,并根据这些信息创建一个 ConfigurationClassBeanDefinition 类型的对象。然后,将方法作为 BeanDefinition 的工厂方法,因为创建对应的 Bean 实例的时候,就是通过当前的方法来创建的。最后,再通过容器的registerBeanDefinition
方法将 BeanDefinition 注册到容器中。
分析完这部分之后,再次回到processConfigBeanDefinitions
方法查看后续的代码。
处理新增的 BeanDefinition
candidates.clear();
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);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
首先将candidates
集合清空,代表目前candidates
集合中的类都处理完毕了。
接下来,判断容器中的 BeanDefinition 是不是比之前更多了,这里多出来的部分主要就是来自于机遇@``Bean
方法创建的 BeanDefinition。后面的步骤,就是将这些新增的 BeanDefinition 找出来,创建对应的 BeanDefinitionHolder 并添加到candidates
中。
这样,只要candidates
不为空,do-while
循环就会接着以同样的逻辑递归处理这些新增的元素,直到没有新的元素被添加进来。
至此,do-while
循环中的内容就分析完了。
结尾部分
接下来,看方法的最后一部分代码。
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
最后就是收尾工作,注册了一个 ImportRegistry 单例对象,并清除了缓存信息。
总结
本文分析了processConfigBeanDefinitions
方法中解析配置类的主要逻辑部分,Spring 会递归处理已经筛选过的配置类以及其中包含的@``Bean
注解标记的方法和配置信息。
下一篇开始分析另一个后处理器方法postProcessBeanFactory
。
转载自:https://juejin.cn/post/7151041170510970893