盘点 Spring Conditional

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜

文章合集 : 🎁 juejin.cn/post/694164…

Github : 👉 github.com/black-ant

CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

这一篇来看一下Conditional的使用和原理 , 先来看一下整体的体系结构

System-SpringBootCondition.png

二 . 使用

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下。
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下。
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下。
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下。
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件。
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web项 目的条件下

2.1 基础使用

2.2 自定义 Conditional

1
2
3
4
5
6
7
8
9
10
11
java复制代码public class DefaultConditional implements Condition {

private Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
logger.info("------> 进行 Conditional 判断 <-------");

return false;
}
}

2.3 基础使用

1
2
3
4
5
6
java复制代码@Bean
@ConditionalOnBean(TestService.class)
public ConfigBean getConfigBean() {
logger.info("------> 开始加载 ConditionalOnBean <-------");
return new ConfigBean();
}

三 . 自定义原理分析

3.1 Conditional 入口

对于 Configuration.@Bean 的创建方式 , Conditinal 的起点是 refush# invokeBeanFactoryPostProcessors , 在其中会调用 ConfigurationClassPostProcessor 进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {

// Step 1 : 获取当前 Configuration 中 @Bean 的元数据信息
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();

// 判断是否应该跳过当前 Bean
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}
}

3.2 Conditional 判断的流程

ConditionEvaluator 是核心处理类 , 最终都会调用 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
java复制代码// C- ConditionEvaluator
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {

// Step 1 : 如果当前 Bean 不包含 @Conditional , 则直接返回
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}

// Step 2 : 有2种 ConfigurationPhase 的类型 , 表示2种配置的阶段
// PARSE_CONFIGURATION :Condition应该在解析@Configuration类时进行计算 , 如果此时条件不匹配,则不会添加@Configuration类
// REGISTER_BEAN : 条件不会阻止@Configuration类被添加 , 在评估条件时,所有@Configuration类都将被解析
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}

// Step 3 : 获取所有的 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);
}
}

// Step 4 : 排序
AnnotationAwareOrderComparator.sort(conditions);

for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// Step 5 : 最终的 Condition 匹配过程
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}

return false;
}

直到这里就开始匹配到对应的方法

四 .常规加载方式

解析

已知的Conditional 是基于 SpringBootCondition 实现的 , 其具体抽象类为 FilteringSpringBootCondition , 看一下主要的继承关系

System_FilteringSpringBootCondition.png

去除不需要配置的类

第一步是快速去除不需要的类 , 主要流程如下 :

  • 起点 : AbstractApplicationContext # refresh # invokeBeanFactoryPostProcessors
  • 处理 : ConfigurationClassPostProcessor # postProcessBeanDefinitionRegistry 处理主要逻辑
  • 拦截 : ConfigurationClassFilter # filter
  • 匹配 : FilteringSpringBootCondition # match

注意 , 这里是进行 match 匹配 ,目的是获取基于正在导入的Configuration类的AnnotationMetadata的AutoConfigurationEntry

4.1 Filter 拦截

Filter 拦截是在 ConfigurationClassFilter ,其中会对所有的 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
37
38
java复制代码private static class ConfigurationClassFilter {

// 自动配置的元数据
private final AutoConfigurationMetadata autoConfigurationMetadata;

List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();

// 此处为所有的 Confiturations 类
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;

// 此处包含 OnBeanCondition , OnClassCondition ,OnWebApplicationCondition 三种
for (AutoConfigurationImportFilter filter : this.filters) {
// 获取是否 存在 match 匹配
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}

// 如果不能跳过 , 记录当前 Confiturations 类
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
return result;
}

}

4.2 FilteringSpringBootCondition 中 match 匹配

此处是重写了其父类的 match 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
// Step 1 : 准备 Report 对象 , 用于记录
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
// Step 2 : 获取对应的所有的 Condition 方法
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);

boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {

// 对match中的数组进行赋值,当outcomes对应下标的ConditionOutcome匹配时为true.其他情况,返回false
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if (!match[i] && outcomes[i] != null) {
// 记录日志
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
// 像 ConditionEvaluationReport # SortedMap 存放评估条件
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}

ConditionEvaluationReport 的作用

该对象用来记录自动化配置过程中条件匹配的详细信息及日志信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public final class ConditionEvaluationReport {

//bean名称
private static final String BEAN_NAME = "autoConfigurationReport";

//创建一个父的条件匹配对象
private static final AncestorsMatchedCondition ANCESTOR_CONDITION = new AncestorsMatchedCondition();

//存放类名或方法名(key),条件评估输出对象(value)
private final SortedMap<String, ConditionAndOutcomes> outcomes = new TreeMap<>();

//是否是原始条件匹配对象
private boolean addedAncestorOutcomes;
//父的条件评估报告对象
private ConditionEvaluationReport parent;

//记录已经从条件评估中排除的类名称
private final List<String> exclusions = new ArrayList<>();
//记录作为条件评估的候选类名称
private final Set<String> unconditionalClasses = new HashSet<>();

}

4.3 getOutcomes 获取

此处以 OnBean 为例 , 此处存在一定的关联关系 :

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
java复制代码protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {

// Step 1 : 初始化一个和处理类等容量的数组
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];

// Step 2 : 遍历所有的 autoConfigurationClasses
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];

if (autoConfigurationClass != null) {

Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
// 判断是否存在 ConditionalOnBean 标注的方法
outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);

// 判断是否需要输出 ConditionOutcome
if (outcomes[i] == null) {
Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
"ConditionalOnSingleCandidate");
// 此处是返回是否要处理
outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
}
}
}
return outcomes;
}

4.4 如何判断是否符合评估条件

注意 , 这里的 matches 和 FilteringSpringBootCondition 不是一个

  • FilteringSpringBootCondition # match : 基于 AutoConfigurationImportFilter , 对给定的自动配置类候选应用筛选器
  • SpringBootCondition # matches : 需要返回最终的判断结果

调用流程

  • 在 loadBeanDefinitionsForBeanMethod 等类似流程种调用 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
java复制代码@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

// 获取注解对应的方法名或者类名
String classOrMethodName = getClassOrMethodName(metadata);
try {

// 获取对应的条件匹配类 , 此处会判断 metadata instanceof , 有限判断是否为 ClassMetadata
ConditionOutcome outcome = getMatchOutcome(context, metadata);

// 很简单的打印日志 , Trace 级别
logOutcome(classOrMethodName, outcome);

// 记录结果 , 通过 ConditionEvaluationReport 和 recordEvaluation 方法实现
recordEvaluation(context, classOrMethodName, outcome);

// 返回是否成功匹配
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException(......);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}

这里的核心就是调用 getMatchOutcome 判断是否符合或者不符合要求 , getMatchOutcome 需要子类重写

五 . getMatchOutcome 详情

5.1 OnClass

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
java复制代码public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Step 1 : 获取当前容器 ClassLoader
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
// Step 2 : 判断是否有 ConditionalOnClass 约束
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
// Step 2-1 : filter 过滤 , 判断是否缺失类
List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
if (!missing.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
}
// Step 2-2 : 构建 matchMessage
matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
.found("required class", "required classes")
.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}

// Step 3 : 同理 , 判断是否需要 MissClasses
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
// .... 与 ConditionalOnClass 基本类似 ,此处省略
}
return ConditionOutcome.match(matchMessage);
}

补充 : ConditionMessage 的作用

5.2 OnBean

与 OnBean 类似 , 这里就展示一种

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
java复制代码public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();


MergedAnnotations annotations = metadata.getAnnotations();

if (annotations.isPresent(ConditionalOnBean.class)) {
// Step 1 :获取 ConditionalOnBean 注解信息
Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
// Step 2 : 返回匹配结果
// 其内部通过 getNamesOfBeansIgnoredByType , getBeanNamesForType 等方式判断类是否存在
MatchResult matchResult = getMatchingBeans(context, spec);
if (!matchResult.isAllMatched()) {
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(spec.message().because(reason));
}
matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
matchResult.getNamesOfAllMatches());
}
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
//.....
}
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
//.....
}
return ConditionOutcome.match(matchMessage);
}

总结

Conditional 本身并不难 , 这一篇主要是为了完善图谱以及后续的 starter 启动流程方案 做准备.

整个流程中有几个环节理解就行了 :

  • Spring 中的 Conditional 都会继承 SpringBootCondition , 会实现其 getOutcomes 方法
  • getOutcomes 是用于快速去掉无需加载的 Configuration , getMatchOutcome 是为了验证匹配关系
  • 通常都会通过 ConditionEvaluator 的 shouldSkip 判断是否需要跳过@Bean 流程

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%