tips: ConfigurationClassParser 是 Springframework 中的重要类。
本章主要是源码理解,有难度和深度,也枯燥乏味,可以根据实际情况选择阅读。
位置:org.springframework.context.annotation.ConfigurationClassParser
ConfigurationClassParser 它是解密 configuration 的关键。理解 ConfigurationClassParser 对理解整个 Spring 框架至关重要。
一、作用是什么
ConfigurationClassParser
是一个非常重要的类,它主要用于解析带有@Configuration
注解的类。
@Configuration
注解表明该类用作配置类,其中可以定义bean和Spring容器应如何初始化和管理这些bean。
ConfigurationClassParser
的作用可以从以下几个方面详细阐述:
- 解析导入的配置:
@Import
注解允许一个配置类导入另一个配置类。ConfigurationClassParser
解析这些@Import
注解,确保所有导入的配置也被处理和应用。 - 处理属性注入:通过
@PropertySource
注解,可以指定一些属性文件,这些属性文件中的属性可以被注入到Spring管理的bean中。ConfigurationClassParser
负责解析这些注解,并确保属性文件被加载且其值可用于注入。 - 处理
@Conditional
注解: Spring框架允许在bean的注册过程中使用条件逻辑,@Conditional
注解及其派生注解(例如@ConditionalOnClass
,@ConditionalOnProperty
等)使得只有在满足特定条件时,才会进行bean的注册。ConfigurationClassParser
负责解析这些条件注解并应用其逻辑。 - processDeferredImportSelectors#processImports 处理扩展配置( Starter 能够被处理的核心分支)
二、触发时机
SpringBoot 应用启动过程中,通过后置处理器去触发 ConfigurationClassPostProcessor。 然后再调用 ConfigurationClassParser
类解析
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
....
}
处理如下:
下面我们将详细分析源码流程。
三、ConfigurationClassPostProcessor
下面是 ConfigurationClassPostProcessor 部分核心代码。
入口方法 processConfigBeanDefinitions(BeanDefinitionRegistry registry) 。开始分析这段代码。
注意这里有一个 do...while
do {
.....
}
while (!candidates.isEmpty());
它将逐一识别和解析配置类,然后将配置类中定义的Bean注册到Spring容器中。这个过程通过不断循环直到没有新的配置类候选者出现为止,确保了所有相关的配置都被完整地处理。
// 创建一个配置类解析器,用于解析和处理配置类信息
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);
// 对解析结果进行验证
parser.validate();
// 从解析器中获取已解析的配置类集合
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
// 移除已经处理过的,避免重复处理
configClasses.removeAll(alreadyParsed);
// 如果读取器未初始化,创建一个配置类Bean定义读取器
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// 加载并注册配置类中定义的Bean
this.reader.loadBeanDefinitions(configClasses);
// 将这批配置类标记为“已解析”
alreadyParsed.addAll(configClasses);
// 清空候选者集合,为下一轮寻找新候选者做准备
candidates.clear();
// 检查是否有新的Bean定义被注册(可能由@Configuration类引入)
if (registry.getBeanDefinitionCount() > candidateNames.length) {
// 重新获取所有Bean定义的名称
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());
}
// 遍历新的Bean定义名称,寻找新的配置类候选者
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));
}
}
}
// 更新候选名称列表,以反映新的Bean定义
candidateNames = newCandidateNames;
}
// 如果还有未处理的候选者,继续循环
} while (!candidates.isEmpty());
特别说明,对于 Bean 的加载和实例化不在本范围了,不进行讲解。感兴趣可以阅读相关章节。
上面的这段代码是 ConfigurationClassPostProcessor 核心。解析来的重头戏。ConfigurationClassParser
四、ConfigurationClassParser
从这行代码开始入手parser.parse(candidates);
- parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
- processDeferredImportSelectors(); // 处理前面推迟的ImportSelector,一些二方包的导入类,将在这个方法中实现。 例如,我们配置在 Starter Spring.factories 中的自动导入类,将在这一环境被加载
parse方法入口
protected void processConfigurationClass(ConfigurationClass configClass) 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);
}
// 如果已存在的配置类不是导入的,则忽略当前导入的配置类,保留现有的非导入类
return;
}
else {
// 如果找到显式的bean定义,可能是意在替换一个导入的类。
// 移除旧的配置类,采用新的配置类。
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// 递归处理配置类及其超类层次结构
SourceClass sourceClass = asSourceClass(configClass);
do {
// 处理当前配置类并更新sourceClass为配置类的超类,准备下一轮处理
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
} while (sourceClass != null); // 如果sourceClass为null,表示超类已经处理完毕
// 将处理完的配置类放入配置类映射中,标记为已处理
this.configurationClasses.put(configClass, configClass);
}
上面可以理解,解析 MyApplication 类所在工程中的类。最终的解析由 doProcessConfigurationClass 实现
processDeferredImportSelectors
负责处理那些被延迟的特殊接口,使用它来按需动态地导入配置。
这些常常依赖于某些条件才被执行,所以被延迟处理。
// 定义处理延迟的ImportSelector的方法
private void processDeferredImportSelectors() {
// 获取之前收集的所有延迟处理的ImportSelector
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
// 将引用置为null,表示开始处理过程,防止重复处理
this.deferredImportSelectors = null;
// 如果没有需要处理的延迟ImportSelector,则直接返回
if (deferredImports == null) {
return;
}
......
// 遍历所有的延迟ImportSelector
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
// 获取与当前ImportSelector相关联的配置类
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
// 调用ImportSelector的selectImports方法,获取所有的导入类名
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
// 处理这些导入的类,将它们作为配置类进行进一步的处理
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
}
}
}
这个方法的主要作用将是找出符合条件的 imports 类。最终还是由processImports() 处理。
到这里,spring.factories 符合条件的一些类将被加载。核心代码deferredImport.getImportSelector().selectImports(configClass.getMetadata());
到这里我们基本上了解了 Starter 是如何被引入进来的。
真正解析的方法 doProcessConfigurationClass
doProcessConfigurationClass
它递归地处理嵌套类、处理@PropertySource注解、处理@ComponentScan注解、处理@Import注解、处理@ImportResource注解、处理@Bean方法、处理接口上的默认方法,最后处理父类
- 处理成员类:方法首先递归地处理配置类中定义的任何成员类(嵌套类)。
- 处理
@PropertySource
注解:然后遍历配置类上的所有@PropertySource
注解,这些注解用来指明属性文件的位置。如果当前的环境实现了ConfigurableEnvironment
接口,则处理注解指定的属性源 - 处理
@ComponentScan
注解:接下来,处理配置类上的所有@ComponentScan
注解,这些注解指示Spring扫描特定包下的组件(即带有@Component
、@Service
等注解的类),并注册为Spring容器中的Bean。如果有条件注解指示在此阶段跳过处理,则不执行扫描。 - 处理
@Import
注解:处理配置类上的@Import
注解,这些注解用来导入其他配置类或配置选择器,允许模块化地组织配置。 - 处理
@ImportResource
注解:处理配置类上的@ImportResource
注解,这些注解用于导入XML配置文件。 - 处理
@Bean
方法:收集配置类中所有带有@Bean
注解的方法的元数据,并将它们添加到配置类对象中。这些方法定义了应该由Spring容器管理的Bean。 - 处理接口上的默认方法:如果配置类实现了接口,并在这些接口上定义了默认方法,这些方法也会被处理。
- 处理父类:最后,如果配置类有超类,那么这个方法会检查超类是否也是一个配置类,不是Java内置类,并且还没有被处理过。如果满足条件,则递归地处理这个超类。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// 首先,递归处理任何成员类(嵌套类)
processMemberClasses(configClass, sourceClass);
// 处理所有的@PropertySource注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
// 如果环境实现了ConfigurableEnvironment接口
if (this.environment instanceof ConfigurableEnvironment) {
// 处理@PropertySource注解
processPropertySource(propertySource);
} else {
// 如果环境没有实现ConfigurableEnvironment接口
logger.warn("忽略了[" + sourceClass.getMetadata().getClassName() +
"]上的@PropertySource注解。原因:环境必须实现ConfigurableEnvironment接口");
}
}
// 处理所有的@ComponentScan注解
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
// 如果存在@ComponentScan注解,并且当前阶段不应该跳过
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// 循环处理每个@ComponentScan注解
for (AnnotationAttributes componentScan : componentScans) {
// 配置类上存在@ComponentScan注解 -> 立即执行扫描
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 检查扫描结果中的定义集合,如果有进一步的配置类,递归解析
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(
holder.getBeanDefinition(), this.metadataReaderFactory)) {
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
}
}
}
}
// 处理所有的@Import注解
processImports(configClass, sourceClass, getImports(sourceClass), true);
// 处理所有的@ImportResource注解
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
// 如果@ImportResource注解存在
if (importResource != null) {
// 获取资源位置
String[] resources = importResource.getStringArray("locations");
// 获取资源的阅读器类
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
// 循环处理每个资源
for (String resource : resources) {
// 解析资源位置中的占位符
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
// 把解析后的资源添加到配置类
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// 处理单独的@Bean方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
// 循环处理每个@Bean方法
for (MethodMetadata methodMetadata : beanMethods) {
// 添加@Bean方法到配置类
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 处理接口上的默认方法
processInterfaces(configClass, sourceClass);
// 处理父类,如果存在的话
if (sourceClass.getMetadata().hasSuperClass()) {
// 获取父类名称
String superclass = sourceClass.getMetadata().getSuperClassName();
// 如果父类存在,并且父类不是java.*开头,并且尚未处理过
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
// 记录已知的父类
this.knownSuperclasses.put(superclass, configClass);
// 找到父类,返回其注解元数据并递归处理
return sourceClass.getSuperClass();
}
}
// 没有父类 -> 处理完成
return null;
}
到这里,大体流程我们已经清楚。不再继续针对注解的解析进行讲解,感兴趣可以自行下载源码阅读理解。
五、本章小结
本章是对整 ConfigurationClassParser 进行讲解,它是 Spring framework 中的最核心类。
到这里,Starter 的整个过程已经分析完成,但是针对条件装配,我们将在下一章进行讲解。
已同步发布到公众号:面汤放盐 第七节 ConfigurationClassParser 源码分析 (qq.com)
掘金账号:第七节 ConfigurationClassParser 源码分析 - 掘金 (juejin.cn)