封面来源:碧蓝航线 飓风与沉眠之海 活动CG

0. 前言

0.1 我的近况

转眼间,又到八月底。最近两个月我一直想在一个月内完成 4 次周更,实际情况是仅仅依靠周末两天完成一篇博客还是太难。一方面,我自己也是“半桶水”,写的过程中也是在学习,反复地查阅各种资料是不可避免的,在这个过程,不仅要验证它们,还要把它们转换成自己的内容;另一方面,自己多年来的坏习惯也让我得到了反噬,坐立不安、心率加快时长发生,担心自己下一秒就会因此丧命,每当此时只得放下手中任何娱乐与学习,静躺在床上试图保持镇静以便缓解症状。

这周去了一趟华西,医师似乎对我这种症状见怪不怪,告知我应当每天花费一定的时间去运动,然后就开了一系列检查,这些检查也是需要预约的,运气还不错的我约到了第二天。我记得有个段子,一位外地游客到成都游玩,上了出租车后便让司机带他去成都最热闹的地方,而后司机把他带到了华西,这足以证明华西在西南人民心中的地位。尽管我去的并不是华西本部(但也是影分身),检查当天也早早到了医院,但几乎每项检查都要进行一段时间的排队。

昨晚查阅了检查的电子报告,似乎没有多大问题,但情况究竟如何还要在下周二复查后才能得知。

朋友,衷心祝愿你能够时刻保持身体健康,对了,不要忘记勤加运动、拒绝熬夜。(2024-08-18 记)

0.2 内容概述

言归正传,在 上一篇 中熟悉了 BeanDefinition 中的相关 API,BeanDefinition 是 Spring 中绕不开的一个类,熟悉它的 API 对后续的源码阅读是非常有帮助的,甚至我认为阅读 Spring 源码的第一步就应该是了解 BeanDefinition 中 API 的使用。

经历 BeanDefinition 这个插曲,本文将按计划完成讲解 SpringBoot 自动配置前的最后一块拼图 —— Spring 配置类的解析。

开门见山,Spring 配置类的解析涉及到 ConfigurationClassPostProcessor 类,这个类也与 SpringBoot 的自动配置机制密切相关,因此决定单独成文,深入理解 ConfigurationClassPostProcessor 的作用。

除此之外,先前已经介绍过 @Configuration 注解的 Full 模式和 Lite 模式,其实现也是由 ConfigurationClassPostProcessor 完成的,因此本文在涉及到这些内容时会直接跳过,详情参考 @Configuration 注解的那些事

0.3 阅读建议

Spring 配置类的解析步骤涉及的类与方法的数量非常多,本文行文过程中经常会跳转到主线以外的类或方法,之后经过一番介绍后又回归主线,仅通过对阅读本文难以把握主线,因此推荐将本文作为阅读源码的辅助资料,结合对示例的 Debug 摸清行文逻辑,最终完成对 ConfigurationClassPostProcessor 类的进一步理解。

0.4 术语解释

为了行文的精简,对文中使用的部分术语解释如下:

  • 配置类:被 @Configuration 注解标记的类
  • @Bean 方法、Bean 方法:被 @Bean 注解标记的方法

1. 用法与示例

1.1 @Conditional 注解

1
2
3
4
5
6
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}

@Conditional 只能够作用在类和方法上,并且在使用时需要指定一个 ConditionClass 数组。

只有当所有 Condition 指定的条件都满足时,对应的 Bean 才会被注册到 Spring 容器中。

可以通过以下方式来使用 @Conditional 注解:

  • 作为类级别的注解,应用于任何直接或间接使用 @Component 注解的类,也包括配置类;
  • 作为元注解,用于组合自定义原型注解;
  • 作为方法级别的注解,作用在任何 @Bean 方法上

如果一个配置类也被 @Conditional 注解标记,与该类关联的所有的 @Bean 方法、@Import 注解和 @ComponentScan 注解都将受到这些条件的约束。

注意: 不支持 @Conditional 注解的继承。来自父类或 重写方法的任何条件都不会被考虑。为了强制执行这些语义,@Conditional 注解为被元注解 @Inherited 修饰,此外,任何使用 @Conditional 作为元注解的自定义组合注解也不得被 @Inherited 修饰。

1.2 Condition 接口

1
2
3
4
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Condition 是一个函数式接口,内部的 matches() 方法用于确定条件是否匹配,该方法接收两个参数,并返回一个 boolean 值。

如果返回 true,对应的 Bean 将会被注册到 Spring 容器中,否则会跳过该 Bean 的注册。

接收的两个参数的含义如下:

  • context:条件上下文,通过该参数可以获取到一些 Spring 容器等相关信息
  • metadata:用于获取正在校验的类或方法的元数据

Condition 的使用必须遵循与 BeanFactoryPostProcessor 相同的限制(简单来说就是 matches() 方法会在 BeanFactoryPostProcessor 所在的阶段执行),并且注意绝不能与 Bean 实例进行交互(重写的 matches() 方法内部不能与 Bean 实例交互,因为 matches() 方法的调用时机往往在 Bean 实例化之前)。如果要对配置类的注册条件进行更细粒度的控制,可以考虑实现 ConfigurationCondition 接口。

对于给定类或方法上的多个 Condition,将根据实现的 Ordered 接口和使用 @Order 注解进行排序,排序规则参阅 AnnotationAwareOrderComparator

1.3 使用示例

示例参考:超详细分析Spring的@Conditional注解

项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
D:.
│ pom.xml

└───src
├───main
│ ├───java
│ │ └───indi
│ │ └───mofan
│ │ │ Config.java
│ │ │
│ │ ├───component
│ │ │ MyBean.java
│ │ │ MyController.java
│ │ │ MyDao.java
│ │ │ MyRepository.java
│ │ │ MyService.java
│ │ │
│ │ ├───condition
│ │ │ ControllerCondition.java
│ │ │ DaoCondition.java
│ │ │ FurtherCondition.java
│ │ │ RepositoryCondition.java
│ │ │ ServiceCondition.java
│ │ │
│ │ └───config
│ │ MyFurtherConfig.java
│ │
│ └───resources
└───test
└───java
ConditionalTest.java

也可以通过 链接 在 GitHub 上获取代码文件。

首先定义一系列 Condition 的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Slf4j
public class ControllerCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
log.info(this.getClass().getName());
return true;
}
}

@Slf4j
public class DaoCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
log.info(this.getClass().getName());
return true;
}
}

@Slf4j
public class FurtherCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
log.info(this.getClass().getName());
return true;
}
}

@Slf4j
public class RepositoryCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
log.info(this.getClass().getName());
// 这里是 false
return false;
}
}

@Slf4j
public class ServiceCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
log.info(this.getClass().getName());
return true;
}
}

除了 RepositoryCondition 返回 false 外,其余实现都是返回 true

通过 @Conditional 注解使用 Condition 的实现类:

作为类级别的注解,作用于使用了 @Component 派生注解的类:

1
2
3
4
@Controller
@Conditional(ControllerCondition.class)
public class MyController {
}

作为类级别的注解,作用在配置类上,观察内部的 @Bean 方法能够成功注册 Bean:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@Conditional(FurtherCondition.class)
public class MyFurtherConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}

public class MyBean {
}

同样是在配置类中使用,作用在 @Import 的类上、@Bean 方法上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@ComponentScan
@Configuration
@Import(MyDao.class)
public class Config {

@Bean
@Conditional(ServiceCondition.class)
public MyService myService() {
return new MyService();
}

@Bean
public MyRepository myRepository() {
return new MyRepository();
}
}

@Conditional(DaoCondition.class)
public class MyDao {
}

public class MyService {
}

@Conditional(RepositoryCondition.class)
public class MyRepository {
}

作用在 @Bean 方法上有两种情况:

  1. 直接作用在对应的 @Bean 方法上,比如 myService() 方法
  2. 作用在 @Bean 注册的类上,比如 MyRepository

RepositoryCondition 实现的 matches() 方法返回的是 false,那么 Spring 容器中应该不存在 MyRepository 类型的 Bean?

测试一下

1
2
3
4
5
6
7
@Test
public void testConditional() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
MyRepository repository = context.getBean(MyRepository.class);
// 尽管 Condition 返回了 false,但是容器中还是有对应的 Bean
assertThat(repository).isNotNull();
}
indi.mofan.condition.ControllerCondition
indi.mofan.condition.FurtherCondition
indi.mofan.condition.ControllerCondition
indi.mofan.condition.FurtherCondition
indi.mofan.condition.DaoCondition
indi.mofan.condition.ControllerCondition
indi.mofan.condition.FurtherCondition
indi.mofan.condition.DaoCondition
indi.mofan.condition.ServiceCondition

运行测试方法后,发现 Spring 容器中存在 MyRepository 类型的 Bean,并且打印的日志中不存在 RepositoryCondition 的相关信息,也就是说,程序运行时,没有执行 RepositoryCondition 重写的 matches() 方法。在控制 @Bean 方法注册 Bean 时,在目标类上使用 @Conditional 注解是无效的,应当直接在 @Bean 方法上使用 @Conditional 注解。

2. 源码剖析

2.1 开门见山地说

Spring 配置类的解析由 ConfigurationClassPostProcessor 实现,除此之外,@Conditional@Component 及其派生注解、@Import@ComponentScan@Configuration@Bean 等与 Bean 注册相关的注解的实现都会在 ConfigurationClassPostProcessor 中完成。

简单回顾下 @Configuration 注解的那些事 中对 ConfigurationClassPostProcessor 的讲解:

  • ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,后者是 BeanFactoryPostProcessor 的子接口;
  • BeanDefinitionRegistryPostProcessor 提供了动态注册新的 BeanDefinition 的能力,与 BeanFactoryPostProcessor 相比,它更早被调用;
  • 无论是对 BeanDefinitionRegistryPostProcessor 中独有方法的实现,还是对 BeanFactoryPostProcessor 中方法的实现,内部都会调用名为 processConfigBeanDefinitions() 的方法。

processConfigBeanDefinitions() 方法完成了各种与 Bean 注册相关注解的解析,该方法的实现较长,在 @Configuration 注解的那些事 一文中只对其中获取配置类信息的小部分代码进行了分析,而那剩下的部分则是本文的主要内容。

2.2 processConfigBeanDefinitions

1
2
3
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// --snip--
}

processConfigBeanDefinitions() 方法首先会遍历当前 Spring 容器中所有的 BeanDefinition,从中找出配置类相关的 BeanDefinition,如果没找到,就立即返回。这些内容在 @Configuration 注解的那些事 一文中已经详细介绍过了,不再过多分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// --snip--

// Sort by previously determined @Order value, if applicable
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});

// Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry singletonRegistry = null;
if (registry instanceof SingletonBeanRegistry sbr) {
singletonRegistry = sbr;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}

if (this.environment == null) {
this.environment = new StandardEnvironment();
}

// --snip--
}

与配置相关的 BeanDefinition 可能有多个,将它们按照 @Order 的值进行排序。

之后还会探测在 ApplicationContext(代码实现中使用的是 SingletonBeanRegistry)内部是否存在自定义的 Bean 名称生成策略(bean name generation strategy),如果有的话,将它们设置给某些成员变量。

如果 environment 的值未被显式设置,会默认初始化为 StandardEnvironment

上面这些内容并不是重点,接下里的才是重头戏:解析每个配置类。 💣

2.3 解析每个配置类

继续阅读 processConfigBeanDefinitions() 方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// --snip--

// Parse each @Configuration class
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 {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);
parser.validate();

// --snip--
}
while (!candidates.isEmpty());

// --snip--
}

实例化一个 ConfigurationClassParser 对象,将先前得到的配置类信息传入其 parse() 方法中完成配置类的解析。

进入 parse() 方法内部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
parse(annotatedBeanDef.getMetadata(), holder.getBeanName());
}

// --snip--
}

// 省略异常处理
}

// 延迟处理 ImportSelector,这与 @Import 注解相关,后文会说到
this.deferredImportSelectorHandler.process();
}

通过判断配置类对应的 BeanDefinition 是哪种类型,然后调用不同的、重载的 parse() 方法。

在先前的示例代码中,配置类是通过 @Configuration 注解实现的,对应 AnnotatedBeanDefinition,因此会直接进入第一个分支:

1
2
3
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

将配置类的元数据信息和对应的 beanName 包装成 ConfigurationClass 实例,然后又调用了 processConfigurationClass() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// 判断是否需要跳过当前配置类的解析,@Conditional 注解的实现
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 = null;
try {
sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"I/O failure while processing configuration class [" + sourceClass + "]", ex);
}

// 保存下解析过的配置类
this.configurationClasses.put(configClass, configClass);
}

processConfigurationClass() 方法共有四步:

  1. 判断是否需要跳过当前配置类的解析, 这也是 @Conditional 注解的实现;
  2. 如果已经解析过当前配置类,判断当前解析的配置类是否是被导入的:
    • 如果不是被导入的,证明现在解析的配置类更加明确,移除先前解析过的同名配置类并重新解析;
    • 如果当前配置类是被导入的,并且先前解析的配置类也是被导入的,那么合并这同名配置类;
    • 如果当前配置类是被导入的,但是先前解析的配置类却不是被导入的,先前解析的配置类更加明确,直接 return,忽略对当前配置类的解析。
  3. 递归处理配置类及其超类;
  4. 保存解析过的配置类信息。

重点放在第一步和第三步,其中:

  • 第一步调用 conditionEvaluator 成员变量的 shouldSkip() 方法来判断是否跳过配置类的解析
  • 第三步的重点放在调用 doProcessConfigurationClass() 方法完成对配置类的解析

2.4 shouldSkip

@Conditional 注解的实现由 ConditionEvaluator#shouldSkip() 方法完成,成员变量 conditionEvaluator 的值是在实例化当前对象时一并实例化的:

1
2
3
4
5
6
7
8
9
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
ProblemReporter problemReporter,
Environment environment,
ResourceLoader resourceLoader,
BeanNameGenerator componentScanBeanNameGenerator,
BeanDefinitionRegistry registry) {
// --snip--
this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
}

回到 shouldSkip() 方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata,
@Nullable ConfigurationPhase phase) {
// 配置类上注解的元数据为空,即 metadata == null 时,返回 false,不跳过解析
// 配置类未被 @Conditional 注解标记时,也不跳过解析
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}

// 一般来说,调用时应该传个 phase 的值,当然,也可以不传
if (phase == null) {
// 如果没传 phase,并且目标类是候选的配置类,递归调用 shouldSkip() 方法
// 其实相当于令 phase == PARSE_CONFIGURATION
if (metadata instanceof AnnotationMetadata annotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate(annotationMetadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
// 否则相当于令 phase == REGISTER_BEAN
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}

// 通过反射,获取 @Conditional 注解的 value 指定的 Condition 实例
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}

// 给 Condition 实例排个序
AnnotationAwareOrderComparator.sort(conditions);

// 循环每个 Condition,只要有一个不满足,就返回 true,表示跳过解析
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition configurationCondition) {
requiredPhase = configurationCondition.getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}

// 所有 Condition 都满足,返回 false,不跳过解析
return false;
}

shouldSkip() 方法的实现很简单,可以概括为三步:

  1. 通过反射获取 @Conditional 注解的 value 属性对应的 Condition 实例;
  2. 对获取到的 Condition 实例排个序;
  3. 遍历 Condition 实例,如果有一个条件不满足,就直接返回 true,表示跳过解析。

2.5 processConfigurationClass

回到 ConfigurationClassParser#processConfigurationClass() 方法中,配置类的解析由以下代码完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected void processConfigurationClass(ConfigurationClass configClass, 
Predicate<String> filter) throws IOException {
// --snip--

// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = null;
try {
sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"I/O failure while processing configuration class [" + sourceClass + "]", ex);
}

// --snip--
}

首先利用 asSourceClass() 方法通过 ConfigurationClass 实例和过滤规则 filter 获取到 SourceClass 对象,解析配置类则是由 doProcessConfigurationClass() 方法完成。

asSourceClass() 方法

不详解 asSourceClass() 的实现,知道它是获取 SourceClass 实例的一个工厂方法就行了。

简单介绍下 SourceClass

1
2
3
4
5
6
7
8
private class SourceClass implements Ordered {

private final Object source; // Class or MetadataReader

private final AnnotationMetadata metadata;

// --snip--
}

SourceClass 对类信息(类信息被封装到 ClassMetadataReader 中)进行了包装,使得带注解(注解信息被封装到 AnnotationMetadata 中)的类能够以统一的方式被处理,而不必关心这些类是怎么被加载的。

doProcessConfigurationClass() 方法

利用 asSourceClass() 构造出 SourceClass 对象后,会立即执行 doProcessConfigurationClass() 方法处理 SourceClass 对象(也就是配置类),而这个方法也会返回一个 SourceClass 对象,之后会判断返回值是否为 null,如果不为 null,又会执行该方法处理新返回的 SourceClass 对象。

在先前贴出的代码片段前有这样一段注释:

Recursively process the configuration class and its superclass hierarchy.

这段注释明确概括了这段代码的核心:递归 处理配置类和其超类层级结构(hierarchy 一词译为层级结构)。

也就是说 doProcessConfigurationClass() 返回的是当前处理类的超类对应的 SourceClass 对象?

该方法的源码的很长,每种处理前都有表明具体作用的注释,不如跟随这些注释来窥探其中的奥秘:

1
2
3
4
5
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
// 1. 首先递归处理嵌套类
processMemberClasses(configClass, sourceClass, filter);
}
1
2
3
4
5
// Process any @PropertySource annotations
// 2. 处理 @PropertySource 注解,也包括组合的 @PropertySources 注解
for (AnnotationAttributes propertySource : /***/ ) {
// --snip--
}
1
2
3
4
5
6
// Search for locally declared @ComponentScan annotations first.
// 3. 搜索本地声明的 @ComponentScan 注解
// 所谓本地声明的,其实就是直接存在、明确声明的 @ComponentScan 注解
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class,
MergedAnnotation::isDirectlyPresent);
1
2
3
4
5
6
7
// Fall back to searching for @ComponentScan meta-annotations (which indirectly
// includes locally declared composed annotations).
// 4. 搜索 @ComponentScan 元注解,间接包括本地声明的组合注解
if (componentScans.isEmpty()) {
componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(),
ComponentScan.class, ComponentScans.class, MergedAnnotation::isMetaPresent);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 5. 存在 @ComponentScan 注解,并且配置类在注册 Bean 期间不会被跳过
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// 遍历所有 @ComponentScan 注解
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// 配置类被 @ComponentScan 注解标记,立即执行扫描
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
// 检查扫描出的 BeanDefinition,以获取更多的配置类,并在需要时进行递归解析
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
// --snip--
}
}
}
1
2
3
// Process any @Import annotations
// 6. 处理 @Import 注解
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
1
2
3
4
5
6
7
// Process any @ImportResource annotations
// 7. 处理 @ImportResource 注解
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
// --snip--
}
1
2
3
4
5
6
// Process individual @Bean methods
// 8. 处理配置类里的 @Bean 方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
1
2
3
// Process default methods on interfaces
// 9. 处理接口中的 default 方法
processInterfaces(configClass, sourceClass);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Process superclass, if any
// 10. 处理超类,如果有的话
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
// 找到了超类,返回它的注解元数据,并递归处理
return sourceClass.getSuperClass();
}
}

// No superclass -> processing is complete
// 11. 没有超类,那就完成对配置类的解析
return null;

doProcessConfigurationClass() 的主要作用就是完成配置类的解析,包括配置类里的嵌套类、声明的各种注解、内部的各种方法,甚至是超类中的信息。

本节对 doProcessConfigurationClass() 的认识只是管中窥豹,并不涉及具体的解析逻辑,这会在后文补充。

2.6 校验每个配置类

再回到 ConfigurationClassPostProcessor#processConfigBeanDefinitions() 方法中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// --snip--

Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);
parser.validate();

// --snip--
}
while (!candidates.isEmpty());

// --snip--

先前解析出的配置类信息通过调用 parser.parse(candidates) 方法完成解析,前三节内容深入这个方法,大致介绍了其中的解析逻辑。

而后调用 parser.validate() 方法,对每个配置类(包括通过解析先前配置类得到的更多的配置类)进行校验。

1
2
3
4
5
6
// org.springframework.context.annotation.ConfigurationClassParser#validate
public void validate() {
for (ConfigurationClass configClass : this.configurationClasses.keySet()) {
configClass.validate(this.problemReporter);
}
}

problemReporter,顾名思义,用来汇报问题的。调用其内部的方法时,可能直接抛出异常,也可能打印一些日志,比如这里使用的默认实现 FailFastProblemReporter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FailFastProblemReporter implements ProblemReporter {
// --snip--

@Override
public void fatal(Problem problem) {
throw new BeanDefinitionParsingException(problem);
}

@Override
public void error(Problem problem) {
throw new BeanDefinitionParsingException(problem);
}

@Override
public void warning(Problem problem) {
logger.warn(problem, problem.getRootCause());
}
}

回到 configClass.validate(this.problemReporter) 方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void validate(ProblemReporter problemReporter) {
// 获取配置类上的 @Configuration 注解信息
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());

// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
// 配置类不能是 final 的(CGLIB 的限制),除非设置了 proxyBeanMethods = false
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods")) {
// 校验配置类是不是 final 的
if (this.metadata.isFinal()) {
problemReporter.error(new FinalConfigurationProblem());
}
// 校验配置类中的 Bean 方法
for (BeanMethod beanMethod : this.beanMethods) {
beanMethod.validate(problemReporter);
}
}

// A configuration class may not contain overloaded bean methods unless it declares enforceUniqueMethods=false
// 配置类不能包含重载的 Bean 方法,除非设置了 enforceUniqueMethods = false
if (attributes != null && (Boolean) attributes.get("enforceUniqueMethods")) {
// --snip--
}
}

简单来说,根据配置类上的 @Configuration 注解对配置类进行了校验,同时还校验了配置类中的 Bean 方法。

对 Bean 方法的校验如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void validate(ProblemReporter problemReporter) {
// Bean 方法被声明为 void 返回值类型
// 可能是误用了 @Bean 注解,这个方法可能是作为 init 方法
if ("void".equals(getMetadata().getReturnTypeName())) {
// declared as void: potential misuse of @Bean, maybe meant as init method instead?
problemReporter.error(new VoidDeclaredMethodError());
}

// 静态的 Bean 方法没有更多的限制,立即返回
if (getMetadata().isStatic()) {
// static @Bean methods have no further constraints to validate -> return immediately
return;
}

if (this.configurationClass.getMetadata().isAnnotated(Configuration.class.getName())) {
if (!getMetadata().isOverridable()) {
// instance @Bean methods within @Configuration classes must be overridable to accommodate CGLIB
// 配置类中的 Bean 方法必须可以被重写,以适应 CGLIB
// 所谓可被重写,即方法未被标记为 static、final 或 private
problemReporter.error(new NonOverridableMethodError());
}
}
}

2.7 源源不断地解析

继续回到 ConfigurationClassPostProcessor#processConfigBeanDefinitions() 方法中:

1
2
3
4
5
6
7
8
9
10
11
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// 候选配置类的解析
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);
parser.validate();

// --snip--
}
while (!candidates.isEmpty());

parser.parse() 方法是对候选配置类的解析,之后调用 parser.validate() 对配置类、Bean 方法进行校验,如果校验不过,就抛出异常。

到目前为止,还仅仅是解析了单个配置类,以前文中的使用示例来说,现在只解析了 Config 配置类。

Config 配置类上使用了 @ComponentScan 注解,这会扫描 Config 类所在的包及其子包下的 @Component 注解和它的派生注解,并把那些类交由 Spring 管理。在扫描的范围内,MyController 类被 @Controller 标记,MyFurtherConfig 类被 @Configuration 标记,并且它们都满足配置的 @Conditional 条件。也就是说,当执行完 parser.parse() 方法后,当前 Spring 容器中有以下名称的 BeanDefinition(不包括 Spring 内置的):

  • config
  • myController
  • myFurtherConfig
1
2
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

接下来,通过 parser.getConfigurationClasses() 获取解析得到的配置类信息,注意,这和 Spring 容器中的 BeanDefinition 不一定相同。比如就示例而言,获取到的配置类信息中还额外包含 MyDao 类,这个类是在 Config 上通过 @Import 注解导入的,但它并不在 Spring 容器中。

MyDao 最终肯定是会在 Spring 容器中的,那这是怎么做到的呢?

这来自 Spring 源源不断地解析配置类与加载 BeanDefinition

在这之前,需要明白源码中几个集合的含义:

  • String[] candidateNames:每轮解析前,Spring 容器中存在的 BeanDefinition 名称。其中每轮解析,表示执行一次 do...while 循环

  • Set<BeanDefinitionHolder> candidates:候选配置类对应的 BeanDefinition

  • Set<ConfigurationClass> alreadyParsed:已经解析过的配置类,初始值为空

  • Set<ConfigurationClass> configClasses:调用 parser.parse(candidates) 后得到的配置类信息

以执行到初始化 configClasses 为例,这些集合中的元素情况是:

集合名称 存在的元素
candidateNames config 与 Spring 容器中内置的 BeanDefinition 名称
candidates 仅有 Config 类对应的 BeanDefinition
alreadyParsed
configClasses ConfigMyControllerMyFurtherConfigMyDao

继续阅读源码,接下来执行 configClasses.removeAll(alreadyParsed);,由于 alreadyParsed 依旧为空,因此本行无事发生。

1
2
3
4
5
6
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);

接下来使用 readerconfigClasses 对应的类加载为 BeanDefinitionconfigClasses 中的 MyDao 会在此时被转换为 BeanDefinition,仅此而已?

非也。

loadBeanDefinitions() 还会将配置类中的 Bean 方法也转换为 BeanDefinition,也就是说,此时除 Spring 内置的 BeanDefinition 外,还有如下 BeanDefinition

BeanDefinition 的名称 名称来源
config 对应配置类名称首字母小写
myController 同上
myFurtherConfig 同上
myBean 对应的 Bean 方法的方法名
myService 同上
myRepository 同上
indi.mofan.component.MyDao 使用 @Import 注解导入
1
alreadyParsed.addAll(configClasses);

将这轮解析得到的配置类信息添加到 alreadyParsed 集合中,表示它们已经被解析过,并且容器中也存在对应的 BeanDefinition

1
candidates.clear();

清空候选的配置类信息,为下一轮解析做准备。因为调用 loadBeanDefinitions() 方法后,可能会解析出其他配置类,需要将这些配置类作为候选的配置类信息。

获取新的候选配置类信息方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 当前容器中的 BeanDefinition 数量大于这轮解析前存在的 BeanDefinition 数量
// 这证明解析过程中得到了新的 BeanDefinition,这其中可能存在其他的配置类
if (registry.getBeanDefinitionCount() > candidateNames.length) {
// 获取到当前容器中的 BeanDefinition 名称
String[] newCandidateNames = registry.getBeanDefinitionNames();
// 将这轮解析前的 BeanDefinition 作为旧的候选名称
Set<String> oldCandidateNames = Set.of(candidateNames);
// 已经解析过的配置类 class name
Set<String> alreadyParsedClasses = new HashSet<>();
// alreadyParsedClasses 是由 alreadyParsed 转换得到的
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
// 遍历当前容器中存在的 BeanDefinition 名称
for (String candidateName : newCandidateNames) {
// 它们不包含在 oldCandidateNames 中
// 这些 BeanDefinition 都是本轮解析新增的
if (!oldCandidateNames.contains(candidateName)) {
// 获取到对应的 BeanDefinition
BeanDefinition bd = registry.getBeanDefinition(candidateName);
// 检查 BeanDefinition 是不是可以作为候选的配置类
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
// 新增的 BeanDefinition 不代表就没被解析过了
// @Import 导入的、@ComponentScan 扫描到的都已经被解析过了
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
// 经过重重过滤,还是存在,那它作为新的候选配置类
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
// 更新下一轮解析前,容器中存在的 BeanDefinition
candidateNames = newCandidateNames;
}

candidatescandidateNames 均已被更新,怎么开始下一轮解析呢?

1
2
3
4
do {
// --snip--
}
while (!candidates.isEmpty());

如果存在新的候选配置类,那就开始下一轮解析,以此进行源源不断的解析。

2.8 解析后的完善

注册名为 IMPORT_REGISTRY_BEAN_NAME 的 Bean

processConfigBeanDefinitions() 方法还没有完,还需要做最后的完善。

1
2
3
4
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (singletonRegistry != null && !singletonRegistry.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
singletonRegistry.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}

这里的 singletonRegistry 其实就是表示 Spring 容器,判断容器中是否存在名称为 IMPORT_REGISTRY_BEAN_NAME 的 Bean(注意,是直接注册一个 Bean,而不是 BeanDefinition),如果不存在,就注册一个。

这个 Bean 的类型是 ImportRegistry,用于处理 ImportAware 接口。

1
2
3
4
5
6
7
8
public interface ImportAware extends Aware {

/**
* Set the annotation metadata of the importing @{@code Configuration} class.
*/
void setImportMetadata(AnnotationMetadata importMetadata);

}

ImportAware 接口需要和 @Import 注解搭配使用,如果 @Import 注解导入的配置类实现了 ImportAware 接口,导入的配置类能够获取到 @Import 注解所在配置类的元数据。

@EnableAsync 注解为例:

1
2
3
4
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
// --snip--
}

@EnableAsync 注解用于启用 SpringBoot 对异步方法的支持,该注解的正确使用需要放在配置类上,常置于 SpringBoot 的主启动类上(主启动类也是一个配置类)。

导入的 AsyncConfigurationSelector 类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";


/**
* Returns {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration}
* for {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()},
* respectively.
*/
@Override
@NonNull
public String[] selectImports(AdviceMode adviceMode) {
return switch (adviceMode) {
case PROXY -> new String[] {ProxyAsyncConfiguration.class.getName()};
case ASPECTJ -> new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
};
}

}

adviceMode 的值通常是 ProxyAsyncConfiguration,因此 @Import 相当于导入了 ProxyAsyncConfiguration。它是一个配置类,继承 AbstractAsyncConfiguration 抽象类,并实现了 ImportAware 接口。

AbstractAsyncConfiguration 类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration(proxyBeanMethods = false)
public abstract class AbstractAsyncConfiguration implements ImportAware {

@Nullable
protected AnnotationAttributes enableAsync;

// --snip--

@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
// 获取 @Import 注解所在配置类的元数据
// 如果 @EnableAsync 注解作用在主启动类上,相当于获取到主启动类的元数据
// 自然也就能获得到主启动类上 @EnableAsync 注解的信息(又绕回去了)
this.enableAsync = AnnotationAttributes.fromMap(
importMetadata.getAnnotationAttributes(EnableAsync.class.getName()));
if (this.enableAsync == null) {
throw new IllegalArgumentException(
"@EnableAsync is not present on importing class " + importMetadata.getClassName());
}
}

// --snip--
}

存储 PropertySourceDescriptors

1
2
// Store the PropertySourceDescriptors to contribute them Ahead-of-time if necessary
this.propertySourceDescriptors = parser.getPropertySourceDescriptors();

将解析配置类过程中得到的 PropertySourceDescriptor 存储起来,以便将来 AOT 使用。

清除缓存

1
2
3
4
5
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory 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.clearCache();
}

清理外部提供的 MetadataReaderFactory 中的缓存,避免占用过高的内存,同时在配置信息发生变更后再次获取时能够获取到最新的信息。

对于共享缓存来说,这个操作是无用的,因为它将被 ApplicationContext 清理。

3. 详解处理配置类

配置类的处理由 ConfigurationClassParser#doProcessConfigurationClass() 方法完成,前文已经初步认识了这个方法,比如每个步骤的主要含义,但并未进行深入,本节将对其进行补充,抽丝剥茧,层层剖析。

3.1 递归处理成员类

1
2
3
4
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}

如果正在处理的配置类 configClass@Component 注解标记,则需要递归处理每个成员(嵌套)类。

进入 processMemberClasses() 方法内部:

1
2
3
4
5
6
7
8
9
private void processMemberClasses(ConfigurationClass configClass, 
SourceClass sourceClass,
Predicate<String> filter) throws IOException {

Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
// --snip--
}
}

首先获取类中的成员类,如果成员类不为空,才继续执行。

1
2
3
4
5
6
7
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
for (SourceClass memberClass : memberClasses) {
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}

获取到所有成员类后,遍历每个成员类。

如果成员类是候选的配置类,并且排除与当前配置类同名的成员类(防止无限循环处理同一个配置类),将符合条件的成员类被添加到 candidates 列表中。

1
OrderComparator.sort(candidates);

之后对候选的成员配置类排个序(实现 PriorityOrderedOrdered 接口)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (SourceClass candidate : candidates) {
if (this.importStack.contains(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
finally {
this.importStack.pop();
}
}
}

遍历候选的成员类:

  • 如果导入栈中包含当前正在处理的配置类,说明存在循环导入,调用 problemReporter.error() 方法抛个异常。

  • 否则将当前正在处理的配置类添加到导入栈中,之后以相同的方式处理候选的成员配置类,最终将先前添加的配置类弹出栈。

3.2 处理 @PropertySource

1
2
3
4
5
6
7
8
9
10
11
12
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class,
PropertySources.class, true)) {
if (this.propertySourceRegistry != null) {
this.propertySourceRegistry.processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}

通过 AnnotationConfigUtils.attributesForRepeatable() 获取 sourceClass 元数据上的 @PropertySource@PropertySources 注解,之后调用 propertySourceRegistry.processPropertySource() 方法来处理它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void processPropertySource(AnnotationAttributes propertySource) throws IOException {

// 获取 @PropertySource 注解的属性值
// --snip--

PropertySourceDescriptor descriptor = new PropertySourceDescriptor(
Arrays.asList(locations),
ignoreResourceNotFound,
name,
factoryClassToUse,
encoding);
this.propertySourceProcessor.processPropertySource(descriptor);
// AOT 使用
this.descriptors.add(descriptor);
}

processPropertySource() 方法中,先获取 @PropertySource 注解的每个属性值,然后将它们包装成 PropertySourceDescriptor 对象,接着调用 propertySourceProcessor.processPropertySource() 方法来处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void processPropertySource(PropertySourceDescriptor descriptor) throws IOException {
// 获取 descriptor 中的信息,包括 name、encoding、locations 等

for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
for (Resource resource : this.resourcePatternResolver.getResources(resolvedLocation)) {
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
}
catch (RuntimeException | IOException ex) {
// Placeholders not resolvable (IllegalArgumentException) or resource not found when trying to open it
// 占位符处理失败、尝试打开未找到的资源
}
}
}

processPropertySource() 方法中,又会把包装好的 PropertySourceDescriptor 对象再拆开,其中最重要的是 locations,表示要加载的配置文件所在的位置。

从 Spring 6.1 开始,locations 的书写支持资源位置通配符,比如 classpath*:/ config/*.properties

为了处理通配符,需要对每个 location 进行处理,即调用 environment.resolveRequiredPlaceholders() 方法。

之后使用处理过的位置信息去获取资源(配置文件),调用 factory.createPropertySource() 方法将配置文件中的信息封装成 PropertySource 对象,最后调用 addPropertySource() 方法将该对象添加到 Environment 中,这就相当于是将配置文件中的信息绑定到应用上下文了。

怎么创建 PropertySource 对象可以深入 factory.createPropertySource() 方法查看,此处不再叙述。

简单介绍下 addPropertySource() 方法,该方法用于将 PropertySource 对象添加到 Environment 中,在之前会处理多个同名(相同的 name)的 PropertySource 情况。处理方式很简单,将同名的 PropertySource 封装成一个 CompositePropertySource,并保证新添加的 PropertySource 优先级更高。

那为什么会存在多个同名的 PropertySource 呢?

这与 @PropertySource 注解有关:

1
2
3
4
5
6
7
public @interface PropertySource {
String name() default "";

String[] value();

// --snip--
}

@PropertySource 注解可以指定一个 name,但可以对应多个 valuevalue 就是配置文件所处的位置(也就是 location),再解析 @PropertySource 注解后,一个 name 就有可能对应多个 PropertySource 对象(即多个配置文件)。

3.3 处理 @ComponentScan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Search for locally declared @ComponentScan annotations first.
// 首先搜索本地声明的 @ComponentScan 注解
// 所谓“本地声明”,就是直接存在于配置类上的,而不是元注解、通过 @Inherited 继承的形式
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class,
MergedAnnotation::isDirectlyPresent);

// Fall back to searching for @ComponentScan meta-annotations (which indirectly
// includes locally declared composed annotations).
// 没有直接存在的 @ComponentScan 注解,再去搜索 @ComponentScan 元注解
// 这间接包括本地声明的组合注解
if (componentScans.isEmpty()) {
componentScans =
AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class,
MergedAnnotation::isMetaPresent);
}

和先前处理 @PropertySource 注解类似,处理 @ComponentScan 注解的第一步也是先找到配置类上的 @ComponentScan 注解元数据。

1
2
3
4
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// --snip--
}

如果存在 @ComponentScan 注解,并且满足配置类上的 @Conditional 条件,那么就进一步解析 @ComponentScan 注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// 配置类被 @ComponentScan 标记,立即执行扫描
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
// 检查扫描出的 BeanDefinition 以获取其他配置类
// 并在必要时进行递归解析
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}

遍历每个 @ComponentScan 配置的信息执行对应的扫描,再遍历扫描到的 BeanDefinition,检查它们是否是候选的配置类,如果是,再进行递归解析。

重点放在 componentScanParser.parse() 方法,用于根据 @ComponentScan 配置的信息扫描得到 BeanDefinition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
// 类路径下 BeanDefinition 的扫描器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

// 解析 @ComponentScan 的各种属性
// --snip--

// 要扫描的基础路径
Set<String> basePackages = new LinkedHashSet<>();
// 先从 basePackages 中获取
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
// 再从 basePackageClasses 获取,并追加到 basePackages 中
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 如果 basePackages 和 basePackageClasses 都没配置扫描路径
// 以配置类所在的包路径进行扫描
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}

scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
// 一切准备脱单后,执行 doScan
return scanner.doScan(StringUtils.toStringArray(basePackages));
}

和解析 @PropertySource 类似,先收集注解,再遍历注解并提取出其中的信息,然后执行对应的逻辑。

目前来看,扫描的逻辑由 scanner.doScan() 完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 遍历每个路径
for (String basePackage : basePackages) {
// 根据路径扫描得到 BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 遍历得到的 BeanDefinition
for (BeanDefinition candidate : candidates) {
// 补充 scope 信息
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 获取 beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition abstractBeanDefinition) {
// 进一步追加一些信息
postProcessBeanDefinition(abstractBeanDefinition, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {
// 根据一些常用注解再对 BeanDefinition 进行补充
// 比如 @Lazy、@Primary、@DependsOn、@Role、@Description
AnnotationConfigUtils.processCommonDefinitionAnnotations(annotatedBeanDefinition);
}
// 检查 BeanDefinition 是否需要被注册
// 比如是否已经注册过同名的 BeanDefinition
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 处理代理
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册 BeanDefinition
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}

整体逻辑比较“朴实无华”:

  • 遍历每个扫描路径,得到 BeanDefinition
  • 遍历得到的 BeanDefinition,对其内部信息进行一些补充
  • 检查 BeanDefinition 是否需要被注册,检查通过才注册

重点在 findCandidateComponents(),它是如何根据路径信息扫描得到 BeanDefinition 的呢?

findCandidateComponents() 方法内部包含许多异常信息,这里不再贴源码。

1
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);

先根据路径信息得到一系列 Resource 对象。

1
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

根据 Resource 信息得到元数据信息。

1
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);

然后把 metadataReader 包装成 BeanDefinition,最后返回即可。

更深层次的实现比较复杂,感兴趣可以自行查看,阅读源码也不用太过深入,以免陷入无底洞。

3.4 处理 @Import

再回到 ConfigurationClassParser#doProcessConfigurationClass() 方法中继续配置类的下一步解析,接下来将解析 @Import 注解:

1
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

getImports()

先看 getImports() 方法:

1
2
3
4
5
6
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}

这里有两个 Set 集合,importsvisited,前者作为结果集返回,后者进一步传入了 collectImports() 方法。

如果刷过图相关的算法题,或者工作中处理过循环引用的情况,visited 这个名字应该会很熟悉。一般来说,它常用于标记已经访问过的信息,防止出现无限递归。

进入 collectImports() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void collectImports(SourceClass sourceClass, 
Set<SourceClass> imports,
Set<SourceClass> visited)
throws IOException {

// 判断 sourceClass 是否已经被导入过
if (visited.add(sourceClass)) {
// 获取 sourceClass 上的每个注解
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
// 递归处理注解,一个类上可能存在多个 @Import 注解
// 比如配置类上使用的各种 @EnableXXX 内部可能会有 @Import
// 配置里自己也可以声明 @Import
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
// 将 @Import 导入的配置类添加到结果集中
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}

processImports()

@Import 通常有三种使用方式:

  1. 最简单的,直接导入配置类
  2. 导入 ImportSelector 的实现类
  3. 导入 ImportBeanDefinitionRegistrar 的实现类

对于第二点,ImportSelector 中的 selectImports() 返回了需要导入的配置类的全限定类名,这些名称通常会从文件中读取。

对于第三点,ImportBeanDefinitionRegistrar 中的 registerBeanDefinitions() 方法提供了另一种注册 BeanDefinition 的方法。

processImports() 方法中需要对这三种方式进行实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void processImports(ConfigurationClass configClass,
SourceClass currentSourceClass,
Collection<SourceClass> importCandidates,
Predicate<String> exclusionFilter,
boolean checkForCircularImports) {

// 存在需要导入的配置类
if (importCandidates.isEmpty()) {
return;
}

// 处理循环导入
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
// 处理导入 ImportSelector 的情况
// --snip--
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 处理导入 ImportBeanDefinitionRegistrar 的情况
// --snip--
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
// 处理直接导入配置类的情况
// --snip--
}
}
}
// 一些异常处理
// --snip--
finally {
this.importStack.pop();
}
}
}

导入 ImportSelector

1
2
3
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);

首先获取 ImportSelector 实例,毕竟 Java 是极致面向对象的语言,没有对象实例,仅靠一个类咋玩呢?

1
2
3
4
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}

processImports() 传入的过滤器和 ImportSelector 自带的过滤器进行合并(or 一下)。

1
2
3
if (selector instanceof DeferredImportSelector deferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector);
}

如果 selectorDeferredImportSelector 实例,需要额外处理下。

Deferred 意为推迟、延迟,也就是说不立即导入对应的类,而是向后推迟,那推迟到什么时候呢?

额外处理的逻辑很简单,其实就是想需要推迟的导入添加到另一个集合中。

当所有的配置类都处理完之后,再处理这些推迟的导入。这在 ConfigurationClassParser#parse() 方法的最后一行能看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
// 解析配置类最初的入口
parse(annotatedBeanDef.getMetadata(), holder.getBeanName());
}
// --snip--
}
// --snip--
}

// 执行推迟的导入
this.deferredImportSelectorHandler.process();
}

继续回到主逻辑中,如果 selector 不是 DeferredImportSelector 实例,就立即执行导入逻辑:

1
2
3
4
5
6
// 获取导入的类的全限定类名
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
// 将类名包装成 SourceClass 对象
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// 递归导入
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);

指定的全限定类名仍有可能是 ImportSelectorImportBeanDefinitionRegistrar 的子类,因此进行递归导入。

导入 ImportBeanDefinitionRegistrar

1
2
3
4
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);

和导入 ImportSelector 类似,同样需要先获取 ImportBeanDefinitionRegistrar 实例。

1
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());

ImportBeanDefinitionRegistrar 提供了另一种注册 BeanDefinition 的方式,但并不会直接在这里注册,而是先保存下 ImportBeanDefinitionRegistrar 实例,后续统一执行实例方法进行注册。

先混个眼熟,这些实例是保存在 importBeanDefinitionRegistrars 属性中的。

至于到底是在哪注册的,后文会进一步介绍。

导入配置类

在这一步中,也只是先保存需要导入的配置类信息,而不是直接将配置类转换成 BeanDefinition 进行注册:

1
2
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());

同样先混个眼熟,这些配置类信息是保存在 ConfigurationClassParser 实例的 importStack 字段中。

对于这些配置类自然也需要进一步解析,因此调用 processConfigurationClass() 方法完成:

1
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);

总结

到此,@Import 注解已经处理完毕,但可以看到的是,无论导入的是什么类,都没有立即将它们转换成 BeanDefinition 并进行注册,而是将需要导入的类保存起来,后续统一处理。

3.5 处理 @ImportResource

回归主线,进行解析配置类的下一步:处理 @ImportResource 注解。

@ImportResource 用于将外部的配置文件(比如 XML 配置文件)导入到基于 Java 配置的 Spring 应用程序中,这在将 Spring 从传统的 XML 配置向 Java 配置迁移的过程中发挥了重要的作用。

第一步同样是先获取 @ImportResource 注解的信息:

1
2
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);

然后获取 @ImportResource 注解的属性值:

1
2
3
4
5
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
// --snip--
}

配置文件的位置会有多个,接下来自然是遍历这些位置信息。

这里没有执行获取配置文件信息的逻辑,而仅仅是将位置信息保存下来:

1
2
3
4
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}

继续混眼熟,配置文件的位置信息存放在 ConfigurationClass 实例的 importedResources 字段中。

3.6 处理 @Bean 方法

1
2
3
4
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

@Bean 方法的处理也是才去相同的策略:

  • 先获取到 @Bean 方法
  • 并不立即处理,而是将它们保存到 ConfigurationClass 实例的 beanMethods 字段中

至于如何获取 @Bean 方法也很简单。现在已经有配置类对应的 Class 对象了,通过反射能够拿到内部所有的方法,再判断下哪些方法被 @Bean 注解标记就完事了。

3.7 处理 default 方法

default 方法的处理由 processInterfaces() 实现:

1
processInterfaces(configClass, sourceClass);

内部实现与处理 @Bean 方法类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void processInterfaces(ConfigurationClass configClass,
SourceClass sourceClass)
throws IOException {
// 首先获取配置类的接口信息
for (SourceClass ifc : sourceClass.getInterfaces()) {
// 获取其中的 Bean 方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
// 遍历 Bean 方法
for (MethodMetadata methodMetadata : beanMethods) {
// 非抽象的 Bean 方法才是 default 方法
if (!methodMetadata.isAbstract()) {
// 同样将方法信息保存在 beanMethods 字段中
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
}
// 递归处理接口的父接口
processInterfaces(configClass, ifc);
}
}

default 方法的处理与 @Bean 方法类似,最后甚至也是将它们保存在 ConfigurationClass 实例的 beanMethods 字段中。

3.8 处理超类

配置类的解析终于来到最后一步,获取当前配置类的超类,如果有超类就让 doProcessConfigurationClass() 返回,否则返回 null

1
2
3
4
5
6
7
8
9
10
11
12
13
// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}

// No superclass -> processing is complete
return null;

在调用 doProcessConfigurationClass() 方法的上层实现中,会判断是否返回了超类,如果存在,就进行递归处理,这在前文中也提到过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ConfigurationClassParser#processConfigurationClass()
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// --snip--

SourceClass sourceClass = null;
try {
sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
// 返回的超类不为 null,递归处理
while (sourceClass != null);
}

// --snip--
}

3.9 总结

ConfigurationClassParser#doProcessConfigurationClass() 方法中对配置类的各种注解、各种方法都进行了处理,除了处理 @ComponentScan 注解是将目标类转换成 BeanDefinition 外,其他处理基本都采取了“延迟”策略,也就是先收集,后续统一处理,而 doProcessConfigurationClass() 方法中是不涉及统一处理的。

盘点一下收集了哪些内容:

  • 导入的 DeferredImportSelector 类存放在 ConfigurationClassParser 实例的 deferredImportSelectorHandler 字段中
  • 导入的 ImportBeanDefinitionRegistrar 类存放在 ConfigurationClass 实例的 importBeanDefinitionRegistrars 字段中
  • 导入的普通配置类存放在 ConfigurationClassParser 实例的 importStack 字段中
  • 通过 @ImportResource 注解导入的配置文件存放在 ConfigurationClass 实例的 importedResources 字段中
  • @Bean 方法、default 方法存放在 ConfigurationClass 实例的 beanMethods 字段中

也就是说,如果需要统一处理这些信息,至少需要 ConfigurationClassParserConfigurationClass 两种实例。

先前已经分析过,导入的 DeferredImportSelector 最终会在 ConfigurationClassParser#parse() 方法最后执行,这也能划分到 doProcessConfigurationClass() 方法的一部分。

因此,如果需要统一处理这些信息,仅需要 ConfigurationClass 实例和 ConfigurationClassParser 实例中的 importStack 信息。

也真是因为 doProcessConfigurationClass() 方法内部更多的是对信息的收集,因此通过示例对调用 ConfigurationClassParser#parse() 方法进行 Debug 时发现并未增加多少 BeanDefinition,除了配置类本身 Config 外,额外的两个是 MyFurtherConfigMyController,它们都是通过 @ComponentScan 扫描得到的。

那统一处理是在哪呢?

4. 统一处理

目光回到最外层的 ConfigurationClassPostProcessor#processConfigBeanDefinitions() 方法中。

以最初的示例代码而言,调用 ConfigurationClassParser#parse() 方法后,BeanDefinition 的数量并未显著增加,在调用 reader.loadBeanDefinitions() 方法加载 BeanDefinition 后,@Bean 方法、导入的配置类都出现在 BeanDefinition 中。

统一处理由 reader.loadBeanDefinitions() 方法完成。

统一处理BeanDefinition

可以看到,构造 reader 对象时传入了 ConfigurationClassParser 实例中的 importStack 信息,调用 loadBeanDefinitions() 方法时,传入了 ConfigurationClass 实例,基本满足先前分析的进行统一处理的要求。

1
2
3
4
5
6
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}

首先构造 TrackedConditionEvaluator 实例,用于处理 @Conditional 注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private class TrackedConditionEvaluator {

private final Map<ConfigurationClass, Boolean> skipped = new HashMap<>();

public boolean shouldSkip(ConfigurationClass configClass) {
Boolean skip = this.skipped.get(configClass);
// 当前配置类未执行过 shouldSkip
if (skip == null) {
// 如果当前配置类是被导入的
if (configClass.isImported()) {
boolean allSkipped = true;
// 递归判断导入当前配置类的配置类是否全被跳过
for (ConfigurationClass importedBy : configClass.getImportedBy()) {
if (!shouldSkip(importedBy)) {
allSkipped = false;
break;
}
}
// 如果导入当前配置类的所有配置类都被跳过
// 那么当前配置类也应该被跳过
if (allSkipped) {
skip = true;
}
}
// 当前配置类不是被导入的
// 或者并不是所有导入当前配置类的配置类都被跳过
if (skip == null) {
// 执行当前配置类的 shouldSkip
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
}
// 缓存一下
this.skipped.put(configClass, skip);
}
return skip;
}
}

然后遍历每个配置类,即 ConfigurationClass 实例,经过先前的解析,该实例中的信息已经非常丰富了,怎么利用这些信息完成更多 BeanDefinition 的加载是由 loadBeanDefinitionsForConfigurationClass 方法完成。

4.1 概括

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass,
TrackedConditionEvaluator trackedConditionEvaluator) {

// 当前配置类需要被跳过
if (trackedConditionEvaluator.shouldSkip(configClass)) {
// 获取配置类对应的 beanName
String beanName = configClass.getBeanName();
// 由于当前配置类需要被跳过,因此要从 registry 中移除
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
// 同样的,还要从 importRegistry 中移除
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}

// 加载 @Import 导入的配置类的 BeanDefinition
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// 加载 @Bean 方法对应的 BeanDefinition
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}

// 加载 @ImportResource 注解导入的配置文件的 BeanDefinition
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 加载导入的 ImportBeanDefinitionRegistrar 注册的 BeanDefinition
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

根据以上信息可以总结出下表:

来源 存储位置 实现方法
@Import ConfigurationClassParser 实例的 importStack 字段 registerBeanDefinitionForImportedConfigurationClass()
@Bean 方法 ConfigurationClass 实例的 beanMethods 字段 loadBeanDefinitionsForBeanMethod()
@ImportResource ConfigurationClass 实例的 importedResources 字段 loadBeanDefinitionsFromImportedResources()
导入的 ImportBeanDefinitionRegistrar ConfigurationClass 实例的 importBeanDefinitionRegistrars 字段 loadBeanDefinitionsFromRegistrars()

4.2 处理 @Import

统一处理 @Import 导入的配置类由 registerBeanDefinitionForImportedConfigurationClass() 方法完成。

1
2
AnnotationMetadata metadata = configClass.getMetadata();
AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);

首先将导入的配置类包装成 BeanDefinition

1
2
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
configBeanDef.setScope(scopeMetadata.getScopeName());

补充 scope 信息。

1
2
String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);

处理导入配置类上一些常见的注解,比如 @Lazy@Primary@DependsOn@Role@Description 等。

1
2
3
4
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
configClass.setBeanName(configBeanName);

处理代理,最终注册 BeanDefinition 即可。

整个过程似曾相识,和处理 @ComponentScan 注解将扫描到的类转换成 BeanDefinition 并注册基本一样。

4.3 处理 @Bean 方法

统一处理 @Bean 方法由 loadBeanDefinitionsForBeanMethod() 方法完成。

1
2
3
4
5
6
7
8
9
10
11
12
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();

// Do we need to mark the bean as skipped by its condition?
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}

首先获取 @Bean 方法的元数据(即 metadata)和方法名称,并判断当前 @Bean 方法是否需要跳过。

1
2
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
Assert.state(bean != null, "No @Bean annotation attributes");

获取 @Bean 注解的信息。

1
2
3
4
5
6
7
8
// Consider name and any aliases
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
String beanName = (!names.isEmpty() ? names.remove(0) : methodName);

// Register aliases even when overridden
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}

处理别名。

1
2
3
4
5
6
7
// Has this effectively been overridden before (e.g. via XML)?
if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
throw new BeanDefinitionStoreException( /***/ );
}
return;
}

判断当前 @Bean 方法对应的 BeanDefinition 是否是要覆盖已有的 BeanDefinition

如果是覆盖了已有的,直接返回或者抛异常。

1
2
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));

构造 @Bean 方法对应的 BeanDefinition

接下来是对构造的 BeanDefinition 进行补充与处理代理,最终调用 BeanDefinitionRegistryregisterBeanDefinition() 方法完成注册。

4.4 处理 @ImportResource

统一处理 @ImportResource 注解导入的配置文件由 loadBeanDefinitionsFromImportedResources() 方法完成。

该方法接收的参数列表如下:

1
Map<String, Class<? extends BeanDefinitionReader>> importedResources

Mapkey 是配置文件所在的位置,value 是解析文件使用的 Reader

1
2
3
importedResources.forEach((resource, readerClass) -> {
// --snip--
});

遍历传入的 Map 依次进行处理。

1
2
3
4
5
6
7
8
9
10
11
// Default reader selection necessary?
if (BeanDefinitionReader.class == readerClass) {
if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) {
// When clearly asking for Groovy, that's what they'll get...
readerClass = GroovyBeanDefinitionReader.class;
}
else {
// Primarily ".xml" files but for any other extension as well
readerClass = XmlBeanDefinitionReader.class;
}
}

根据不同的文件类型,选择不同的 reader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<>();

// --snip--

BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
if (reader == null) {
try {
// Instantiate the specified BeanDefinitionReader
reader = readerClass.getConstructor(BeanDefinitionRegistry.class)
.newInstance(this.registry);
// Delegate the current ResourceLoader to it if possible
if (reader instanceof AbstractBeanDefinitionReader abdr) {
abdr.setResourceLoader(this.resourceLoader);
abdr.setEnvironment(this.environment);
}
readerInstanceCache.put(readerClass, reader);
}
catch (Throwable ex) {
// --snip--
}
}

尝试从缓存中获取 BeanDefinitionReader 实例,如果获取失败,利用 Class 对象通过反射获取实例。

1
reader.loadBeanDefinitions(resource);

最后使用 reader 加载配置文件中的 BeanDefinition

4.5 处理 ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar 提供了另一种注册 BeanDefinition 的方式,它的处理由 loadBeanDefinitionsFromRegistrars() 方法完成,内部实现极其简单,直接调用 ImportBeanDefinitionRegistrar 里的 registerBeanDefinitions() 方法就完事了:

1
2
3
4
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

5. 总结

ConfigurationClassPostProcessor 类主要完成了对 Spring 配置类的解析,在 @Configuration 注解的那些事 一文中主要介绍了配置类的 Full 模式和 Lite 模式,本文则是在此基础上对 Spring 配置类的详细解析步骤进行了介绍。

在解析配置类的过程中会涉及到多种注解的解析,包括 @Conditional@Component 及其派生注解、@Import@ComponentScan@Configuration@Bean 等与 Bean 注册相关的注解。

ConfigurationClassPostProcessor 以容器中初始存在的配置类作为起点,然后不断寻找更多的配置类,以此进行源源不断地解析。

配置类由 ConfigurationClass 实例表示,在解析过程中,通过对注解、方法的解析不断补充完善这个对象,最后通过 ConfigurationClassBeanDefinitionReader 实例的 loadBeanDefinitions() 方法加载导入的、@Bean 方法对应的 BeanDefinition

6. 写在最后

本文原计划在九月初发布,结果身体上总是有各种不适,胸闷、呼吸困难、胸痛的情况时有发生,给自己的学习和生产造成了极大的影响,每周不是在医院就是在去医院的路上,尽管最终并没有检查出什么,而自己却深陷疑病症的漩涡。

国庆过后,自己身体逐渐好转,胃部的不适成为主要症状,胸闷、呼吸困难的次数也基本屈指可数,对自己的影响大大减小,终于在临近 11 月完成了本文。

总之,拒绝熬夜,不要久坐,保持好心情,减少焦虑,快乐度过每一天。

愿看到这里的你身体永远健康,烦恼永远没有。