Spring 源码阅读 37:postProcessBeanDefinitionRegistry 对 @Configuration 配置的解析和处理(2)

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本文分析了解析配置类的主要逻辑部分,Spring 会递归处理已经筛选过的配置类以及其中包含的`@Bean`注解标记的方法和配置信息。

基于 Spring Framework v5.2.6.RELEASE

接上篇:Spring 源码阅读 36:postProcessBeanDefinitionRegistry 对 @Configuration 配置的解析和处理(1)

概述

这一篇接着上篇,继续分析 ConfigurationClassPostProcessor 中postProcessBeanDefinitionRegistry方法对标记了@Configuration注解的类的解析和处理。

建议两篇一起阅读。

处理过程

下图是postProcessBeanDefinitionRegistry方法中执行具体解析过程的processConfigBeanDefinitions方法的源代码。

image.png

上一篇分析了前半部分,也就是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>)publicvoidparse(Set<BeanDefinitionHolder>configCandidates) {
for (BeanDefinitionHolderholder : configCandidates) {
BeanDefinitionbd=holder.getBeanDefinition();
try {
if (bdinstanceofAnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
         }
elseif (bdinstanceofAbstractBeanDefinition&& ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
         }
else {
parse(bd.getBeanClassName(), holder.getBeanName());
         }
      }
catch (BeanDefinitionStoreExceptionex) {
throwex;
      }
catch (Throwableex) {
thrownewBeanDefinitionStoreException(
"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)protectedfinalvoidparse(AnnotationMetadatametadata, StringbeanName) throwsIOException {
processConfigurationClass(newConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

这里将元数据和 Bean 名称封装成了一个ConfigurationClass 对象作为参数,调用了processConfigurationClass方法。

// org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClassprotectedvoidprocessConfigurationClass(ConfigurationClassconfigClass, Predicate<String>filter) throwsIOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
   }
ConfigurationClassexistingClass=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.SourceClasssourceClass=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方法。

image.png

这个方法也比较长,其核心逻辑就是对当前配置类的配置信息进行解析和处理,包括递归处理内部类的配置信息、@PropertySource注解的配置信息、@ComponentScan注解的配置信息、@Import注解的配置信息、@ImportResource注解的配置信息、被@Bean注解标记的方法,并且,如果包含父类的话,还会返回父类的信息供递归处理。

以上所有被解析的信息,都会作为 ConfigurationClass 对象的内容,保存到解析器的configurationClasses集合中。

回到最初的processConfigBeanDefinitions方法中,当parse方法执行完之后,解析器还会通过validate方法对解析的结果进行验证,确保解析到的都是符合 Spring 规定的配置。

parser.validate();

随后,还会从解析器中获取所有解析得到的 ConfigurationClass,并排除掉alreadyParsed集合中的元素,避免后续重复处理。

Set<ConfigurationClass>configClasses=newLinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

至此,配置类的解析工作就完成了,解析的结果就是所有已经被处理的配置内容,和一个 ConfigurationClass 结合configClasses

@Bean方法的解析

接着看后面的代码。

// Read the model and create bean definitions based on its contentif (this.reader==null) {
this.reader=newConfigurationClassBeanDefinitionReader(
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#loadBeanDefinitionspublicvoidloadBeanDefinitions(Set<ConfigurationClass>configurationModel) {
TrackedConditionEvaluatortrackedConditionEvaluator=newTrackedConditionEvaluator();
for (ConfigurationClassconfigClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
   }
}

这里,对集合中的每一个 ConfigurationClass 调用了loadBeanDefinitionsForConfigurationClass方法。这里的 TrackedConditionEvaluator 是用来通过@Conditional注解来判断是不是要跳过当前的配置类处理。

privatevoidloadBeanDefinitionsForConfigurationClass(
ConfigurationClassconfigClass, TrackedConditionEvaluatortrackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
StringbeanName=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 (BeanMethodbeanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
   }
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

如果当前的配置类是由别的配置类引入的,或者引入了别的配置类,都会有相应的处理。我们此时重点看方法体中for循环的部分,在这里,会通过配置类的getBeanMethods方法,将配置类中所有的 BeanMethod 获取到,并对其执行loadBeanDefinitionsForBeanMethod方法。

从前面对parse方法的解析中可以看到,此处获取到的 BeanMethod 就是当前配置类中所有被标记了@Bean注解的方法集合。我们进入loadBeanDefinitionsForBeanMethod方法中查看源码。

image.png

这个方法的代码量也非常可观。但是大部分逻辑都与之前分析过的 BeanDefinition 加载逻辑类似。首先,会读取@Bean注解的信息,并根据这些信息创建一个 ConfigurationClassBeanDefinition 类型的对象。然后,将方法作为 BeanDefinition 的工厂方法,因为创建对应的 Bean 实例的时候,就是通过当前的方法来创建的。最后,再通过容器的registerBeanDefinition方法将 BeanDefinition 注册到容器中。

分析完这部分之后,再次回到processConfigBeanDefinitions方法查看后续的代码。

处理新增的 BeanDefinition

candidates.clear();
if (registry.getBeanDefinitionCount() >candidateNames.length) {
String[] newCandidateNames=registry.getBeanDefinitionNames();
Set<String>oldCandidateNames=newHashSet<>(Arrays.asList(candidateNames));
Set<String>alreadyParsedClasses=newHashSet<>();
for (ConfigurationClassconfigurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
   }
for (StringcandidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinitionbd=registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(newBeanDefinitionHolder(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 classesif (sbr!=null&&!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactoryinstanceofCachingMetadataReaderFactory) {
// 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

目录
相关文章
|
1天前
|
Java 开发者 Spring
深入解析 @Transactional:Spring 事务管理的艺术及实战应对策略
深入解析 @Transactional:Spring 事务管理的艺术及实战应对策略
6 2
|
1天前
|
存储 缓存 Java
深入解析Spring框架中的ReflectionUtils
深入解析Spring框架中的ReflectionUtils
8 1
|
1天前
|
NoSQL Java Redis
【源码解析】自动配置的这些细节都不知道,别说你会 springboot
【源码解析】自动配置的这些细节都不知道,别说你会 springboot
|
1天前
|
JSON 缓存 Java
Spring Boot中的JSON解析优化
Spring Boot中的JSON解析优化
|
1天前
|
Java 容器 Spring
Spring5源码解析5-ConfigurationClassPostProcessor (上)
Spring5源码解析5-ConfigurationClassPostProcessor (上)
|
1天前
|
Java 测试技术 应用服务中间件
Spring 与 Spring Boot:深入解析
Spring 与 Spring Boot:深入解析
8 0
|
7天前
|
机器学习/深度学习 缓存 算法
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
|
10天前
|
XML Java 数据格式
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
21 3
|
2天前
|
Java 数据库连接 Spring
Spring 整合 MyBatis 底层源码解析
Spring 整合 MyBatis 底层源码解析
|
8天前
|
存储 NoSQL 算法
Redis(四):del/unlink 命令源码解析
Redis(四):del/unlink 命令源码解析

推荐镜像

更多