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

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本文介绍了处理配置类的过程的第一部分,即从所有的 BeanDefinition 中筛选出符合候选条件的配置类对应的 BeanDefinition,以及对这些配置类信息进行解析和处理前的准备工作。

基于 Spring Framework v5.2.6.RELEASE

接上篇:Spring 源码阅读 35:Spring 如何解析 @Configuration 中的配置

概述

上一篇介绍了@Configuration注解标记的类别被 Spring 扫描到之后,是由 ConfigurationClassPostProcessor 来执行配置内容的处理的,在 Spring 上下文加载的过程中,会分别执行 ConfigurationClassPostProcessor 的postProcessBeanDefinitionRegistrypostProcessBeanFactory两个方法。本文来深入分析postProcessBeanDefinitionRegistry方法的原理。

处理过程

我们从 ConfigurationClassPostProcessor 的postProcessBeanDefinitionRegistry方法开始看起。

// org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry@OverridepublicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistryregistry) {
intregistryId=System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
thrownewIllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against "+registry);
   }
if (this.factoriesPostProcessed.contains(registryId)) {
thrownewIllegalStateException(
"postProcessBeanFactory already called on this post-processor against "+registry);
   }
this.registriesPostProcessed.add(registryId);
processConfigBeanDefinitions(registry);
}

这个方法的逻辑比较好理解。

首先对当前执行的registry对象(其实就是当前上下文中的 BeanFactory 容器)获取一个registryId,然后判断registriesPostProcessedfactoriesPostProcessed两个集合中是不是包含了这个registryId。这里的目的是为了确保postProcessBeanDefinitionRegistrypostProcessBeanFactory两个方法不会被重复执行。

方法的最后,通过processConfigBeanDefinitions来执行处理逻辑,我们来看这个方法的源码:

image.png

这个方法的代码量非常大,我们一部分一部分来分析。这里顺便提一句,在 Spring 的源码当中,类似这样包含复杂逻辑的方法,都会在方法体中用空行的方式来分割相对独立的小部分,并用注释说明每一小部分做了什么工作。接下来分析每一部分的时候,会把分析的那一部分代码都贴出来,方便查找。

筛选符合条件的配置类

List<BeanDefinitionHolder>configCandidates=newArrayList<>();
String[] candidateNames=registry.getBeanDefinitionNames();

方法的开头,定义了两个后面要用到的变量。

  • configCandidates用来保存配置类的 BeanDefinitionHolder 对象,目前还是个空集合。
  • candidateNames是从容器中获取到的所有 BeanDefinition 的名字。
for (StringbeanName : candidateNames) {
BeanDefinitionbeanDef=registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) !=null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: "+beanDef);
      }
   }
elseif (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(newBeanDefinitionHolder(beanDef, beanName));
   }
}

遍历candidateNames中所有的 Bean 名称,并从容器中获取到对应的 BeanDefinition,对这个 BeanDefinition 进行判断。

如果它的ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE属性值不是空,那么代表这个 BeanDefinition 已经作为配置类被处理过了,这个判断的作用从代码中记录日志信息中也可以看得出来。

如果没有被处理过,会通过 ConfigurationClassUtils 的方法checkConfigurationClassCandidate对其进行校验,这一步是为了算选出符合条件的配置类,如果是,那么它将会被添加到configCandidates中。

由此可知,这里的for循环的作用,是将容器中所有还没有被处理过的配置类的 BeanDefinition 找出来。

这里,我们再进入checkConfigurationClassCandidate方法的源码,深入分析筛选的逻辑。

image.png

这里的代码量也不少,我们慢慢分析。

StringclassName=beanDef.getBeanClassName();
if (className==null||beanDef.getFactoryMethodName() !=null) {
returnfalse;
}

首先获取 BeanDefinition 的className,如果类型名称为空或者当前类已经制定了工厂方法,那么肯定不是配置类。

完成最初步的筛选后,接着往下看。

AnnotationMetadatametadata;
if (beanDefinstanceofAnnotatedBeanDefinition&&className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...metadata= ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
elseif (beanDefinstanceofAbstractBeanDefinition&& ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
// Check already loaded Class if present...// since we possibly can't even load the class file for this Class.Class<?>beanClass= ((AbstractBeanDefinition) beanDef).getBeanClass();
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||BeanPostProcessor.class.isAssignableFrom(beanClass) ||AopInfrastructureBean.class.isAssignableFrom(beanClass) ||EventListenerFactory.class.isAssignableFrom(beanClass)) {
returnfalse;
   }
metadata=AnnotationMetadata.introspect(beanClass);
}
else {
try {
MetadataReadermetadataReader=metadataReaderFactory.getMetadataReader(className);
metadata=metadataReader.getAnnotationMetadata();
   }
catch (IOExceptionex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: "+className, ex);
      }
returnfalse;
   }
}

这里主要是为了获取 BeanDefinition 的 AnnotationMetadata 注解元数据,可以看到,不管当前的 BeanDefinition 是不是 AnnotatedBeanDefinition 类型的,都会尝试获取 AnnotationMetadata。获取到 AnnotationMetadata 就可以进行下一步的筛选。

Map<String, Object>config=metadata.getAnnotationAttributes(Configuration.class.getName());
if (config!=null&&!Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
elseif (config!=null||isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
returnfalse;
}

先从 AnnotationMetadata 中获取@Configuration注解的配置信息,并封装到一个名为config的 Map 中。接下来进行判断:

  • 如果config不为空,且proxyBeanMethods属性的值为true(默认值),则给 BeanDefinition 设置CONFIGURATION_CLASS_ATTRIBUTE属性的值为full
  • 如果config不为空,且元数据符合配置类的候选条件,则给 BeanDefinition 设置CONFIGURATION_CLASS_ATTRIBUTE属性的值为lite
  • 如果以上两条都不符合,则当前的 BeanDefinition 不符合候选条件,返回false

通过以上筛选之后,将配置类的@Order注解的值赋值给 BeanDefinition 的ORDER_ATTRIBUTE对应的属性。

// It's a full or lite configuration candidate... Let's determine the order value, if any.Integerorder=getOrder(metadata);
if (order!=null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
returntrue;

最后,返回true表示当前的 BeanDefinition 符合配置类的候选条件。

再次回到processConfigBeanDefinitions方法中看后面的代码。

// Return immediately if no @Configuration classes were foundif (configCandidates.isEmpty()) {
return;
}

之后进行判断,如果没有找到符合条件的 BeanDefinition,则方法直接返回。

解析和处理配置类之前的准备工作

// Sort by previously determined @Order value, if applicableconfigCandidates.sort((bd1, bd2) -> {
inti1=ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
inti2=ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
returnInteger.compare(i1, i2);
});

在处理之前,还需要对configCandidates中所有的 BeanDefinition 进行排序,这里排序的一句就是上一部中给 BeanDefinition 添加的排序值,来自配置类的@Order注解。

接下来看下一步。

SingletonBeanRegistrysbr=null;
if (registryinstanceofSingletonBeanRegistry) {
sbr= (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGeneratorgenerator= (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator!=null) {
this.componentScanBeanNameGenerator=generator;
this.importBeanNameGenerator=generator;
      }
   }
}
if (this.environment==null) {
this.environment=newStandardEnvironment();
}

首先判断了registry是不是 SingletonBeanRegistry 类型,registry是当前上下文的容器,从之前的代码分析中可以知道它的类型是 DefaultListableBeanFactory,它是 SingletonBeanRegistry 的实现类,因此这里if语句块的内容会被执行。

这里会判断当前的后处理器是否已经设置过了 Bean 名称生成器,如果没有的话,则从容器中获取,并赋值给当前的后处理器。

然后还会判断environment是不是被初始化了,如果没有则进行初始化。

接着看后续的代码。

// Parse each @Configuration classConfigurationClassParserparser=newConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder>candidates=newLinkedHashSet<>(configCandidates);
Set<ConfigurationClass>alreadyParsed=newHashSet<>(configCandidates.size());

在以上的代码块中,首先创建了一个 ConfigurationClassParser 对象,从名字可以看出它是配置类的解析器,用于后续解析配置类。

然后又创建了两个集合:

  • candidates里存放了前面步骤中筛选出来的所有的配置类,这里虽然用了 Set 集合,但是 LinkedHashSet 是可以保证迭代顺序的,因此之前的排序信息不会受到影响。
  • alreadyParsed集合是一个存放 ConfigurationClass 类型对象的 Set 集合,从名字可以看出来它的作用已经解析过的配置类。

后续流程

至此,就完成了配置类的筛选和解析处理之前的准备工作,之后就是在一个do-while循环里对candidates里的配置类进行解析和处理。

do {
// 配置类的解析和处理}
while (!candidates.isEmpty());

限于本文的篇幅,解析和处理的流程放到下一篇来写。

总结

本文介绍了 ConfigurationClassPostProcessor 后处理器中的postProcessBeanDefinitionRegistry方法处理配置类的过程的第一部分,即从所有的 BeanDefinition 中筛选出符合候选条件的配置类对应的 BeanDefinition,以及对这些配置类信息进行解析和处理前的准备工作。下一篇将继续通过源码深入分析解析和处理的原理。

目录
相关文章
|
1天前
|
存储 缓存 运维
云计算中的服务器选型与配置:技术深度解析
【6月更文挑战第29天】云计算服务器选型与配置深度解析:关注业务需求、技术要求及成本效益。重点包括CPU、内存、存储和网络配置的优化,结合负载均衡、缓存、虚拟化和自动化运维策略,以提升性能和效率,确保云服务的稳定与高效。
|
3天前
|
Java 开发者 Spring
深入解析 @Transactional:Spring 事务管理的艺术及实战应对策略
深入解析 @Transactional:Spring 事务管理的艺术及实战应对策略
9 2
|
3天前
|
存储 缓存 Java
深入解析Spring框架中的ReflectionUtils
深入解析Spring框架中的ReflectionUtils
9 1
|
3天前
|
域名解析 网络协议 Linux
Linux系统下DNS配置指南
Linux系统下DNS配置指南
16 1
|
3天前
|
缓存 监控 Java
深入解析Nacos配置中心的动态配置更新技术
深入解析Nacos配置中心的动态配置更新技术
|
2天前
|
JSON 缓存 Java
Spring Boot中的JSON解析优化
Spring Boot中的JSON解析优化
|
3天前
|
JSON 缓存 Java
Spring Boot中的JSON解析优化
Spring Boot中的JSON解析优化
|
3天前
|
Java 程序员 Spring
Spring 源码阅读 一
Spring 源码阅读 一
|
3天前
|
存储 缓存 监控
JVM中G1垃圾收集器:原理、过程和参数配置深入解析
JVM中G1垃圾收集器:原理、过程和参数配置深入解析
|
3天前
|
Java 容器 Spring
Spring5源码解析5-ConfigurationClassPostProcessor (上)
Spring5源码解析5-ConfigurationClassPostProcessor (上)

推荐镜像

更多