Spring中@Conditional是如何生效的

本文灵感源自马士兵MAC课程-源码五班

案例代码

实体类 Boss 和 Bar

1
2
3
4
5
6
7
package com.eitan.condition.entity;

import org.springframework.stereotype.Component;

@Component
public class Boss {
}
1
2
3
4
package com.eitan.condition.entity;

public class Bar {
}

可以看到,Boss 上是添加了 @Component 注解的,Spring 通过包扫描可以将其添加进容器

BarConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.eitan.condition.config;

import com.eitan.condition.condition.ExistBossCondition;
import com.eitan.condition.entity.Bar;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BarConfiguration {
@Bean
@Conditional(ExistBossCondition.class)
public Bar bar() {
return new Bar();
}
}

Bar 通过配置类将其注入进容器,@Conditional 注解表示必须满足 ExistBossCondition.class 是匹配才会将其注入进容器

ExistBossCondition

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.eitan.condition.condition;

import com.eitan.condition.entity.Boss;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class ExistBossCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getBeanFactory().containsBeanDefinition(Boss.class.getSimpleName().toLowerCase());
}
}

这里的匹配规则是,如果容器中已经存在了 boss 的定义信息,才会将其所影响的类加载进容器

MyApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.eitan.condition;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;

public class MyApplicationContext {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.eitan.condition");
Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
}
}

  • 允许代码后,可以发现打印出了 bar 和 boss
  • 当除去 Boss.class 上的 @Component 之后,bar 和 boss 都不会被打印

源码分析

Boss 和 BarConfiguration 是怎么加载进容器的

AnnotationConfigApplicationContext的构造方法

1
2
3
4
5
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}

发生在 scan(basePackages) 方法中,最终调用的是 ClassPathBeanDefinitionScannerdoSacn 方法

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
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 遍历 basePackages
for (String basePackage : basePackages) {
// 扫描 basePackage 找出符合要求的beanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 遍历
for (BeanDefinition candidate : candidates) {
// 解析@Scope注解,包括scopeName和proxyMode
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 使用beanName生成器生成beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
// 处理beanDefinition对象,例如,此bean是否可以自动装配到其它bean中
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
// 处理定义在目标类上的通用注解,包括@Lazy、@Primary、@DependsOn、@Role、@Description
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 检查beanName是否已经被注册过
if (checkCandidate(beanName, candidate)) {
// 将当前遍历的bean的bean定义和beanName封装为Holder
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 根据proxyMode的值,选择是否创建作用域代理
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册 beanDefinition
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}

继续跟踪 findCandidateComponents(basePackage); 方法,通过 doScan 方法扫描到的 BeanDefinition 都是 ScannedGenericBeanDefinition

查看扫描到的类

可以看到,Boss 和 BarConfiguration 已经被扫描到了,但是 Bar 没有被扫描到

image-20210807150305597

Bar 的加载时机

发生在 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);

processConfigBeanDefinitions(registry);
}

ConfigurationClassPostProcessor 永远是最先被调用的 PostProcessor,其作用是用来处理容器中的配置类

processConfigBeanDefinitions

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
48
49
50
51
52
53
54
55
56
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 存放BeanDefinitionHolder对象的集合
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// 当前registry就是DefaultListableBeanFactory,获取所有已经注册的BeanDefinition的beanName
String[] candidateNames = registry.getBeanDefinitionNames();

// 遍历所有要处理的beanDefinition的名称
for (String beanName : candidateNames) {
// 获取指定名称的BeanDefinition对象
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// 如果beanDef中的configurationClass属性不等于空,那么意味着已经处理过了
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
// 判断当前beanDef是否是一个配置类,并为BeanDefinition设置属性为lite或者full,此处设置属性值是为了进行后续调用
// 如果Configuration配置了proxyBeanMethods代理为true则为full
// 如果加了@Bean、@Component、@ComponentScan、@Import、@ImportSource注解则为lite
// 判断当前beanDef是否加了@Configuration注解或者是@Bean,@Component,@ComponentScan,@Import,@ImportSource注解
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// 添加到对应的集合中
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}

......

// 配置类的解析类 ConfigurationClassParse
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// 存放 BeanDefinitionHolder 对象
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// 存放扫描包下的所有 bean
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
// 解析 @Controller、@Import、@ImportSource、@ComponentScan、@ComponentScans、@Bean的BeanDefinition
parser.parse(candidates);
parser.validate();

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

// 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);

......
}

loadBeanDefinitionsForConfigurationClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
}
// 从@Bean修饰的方法中加载BeanDefinition
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}

loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

loadBeanDefinitionsForBeanMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();

// Do we need to mark the bean as skipped by its condition?
// 这里就是 @Conditional 生效的地方
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}
....

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
47
48
49
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// metadata为空或者配置类中不存在@Conditional标签
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}

// 采用递归的方式判断,第一次执行是phase必为空
if (phase == null) {
// 下面的逻辑判断中,需要进入ConfigurationClassUtils.isConfigurationCandidate方法,主要逻辑如下
// 1.metadata是AnnotationMetadata类的一个实例
// 2.检测bean中是否使用@Configuration注解
// 3.检测bean不是另一个接口
// 4.检查bean中是否包含@Component、@ComponentScan、@Import、@ImportResource中任意一个
// 5.检查bean中是否有@Bean注解
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}

List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}

// 对相关的条件进行排序操作
AnnotationAwareOrderComparator.sort(conditions);

for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// requiredPhase只可能是空或者是ConfigurationCondition的一个实例对象
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
// 此逻辑为:1.requiredPhase不是ConfigurationCondition的实例
// 2.phase==requiredPhase,从上述的递归可知,phase可为ConfigurationPhase.PARSE_CONFIGURATION或者metadata, ConfigurationPhase.REGISTER_BEAN
// 3.condition.matches(this.context,metadata)返回false
// 如果条件成立,则在此函数上层将阻断bean注入Spring容器
return true;
}
}

return false;
}

这里获取了该方法上的 conditionClasses,然后调用了这些 conditionClasses 的 matches 方法,只要有一个 matches 返回 false,则这个方法就返回 true,上层方法就会跳过这个类的加载

总结

  1. doScan 方法会扫描加载 basePackages 下所有注释了 @Component 的类
  2. @Configuration 包含了 @Component
  3. 在调用 BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessor 时最先调用的就是 ConfigurationClassPostProcessor,该类中解析了对配置类的解析。
  4. 该类处理了 @Component、@ComponentScan、@Import、@ImportSource、@Bean 注解
  5. 在解析配置类或者方法时,会先调用 shouldSkip 方法,该方法中会获取方法或类上修饰的 @Conditional 注解的 conditionClasses
  6. 遍历调用这些 conditionClasses 实现了 Condition 接口的 matches 方法,当有一个返回 false 这跳过解析