网站首页 > 精选文章 / 正文
一 . 前言
这一篇来看一下Conditional的使用和原理 , 先来看一下整体的体系结构
二 . 使用
- @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
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 基础使用
@Bean
@ConditionalOnBean(TestService.class)
public ConfigBean getConfigBean() {
logger.info("------> 开始加载 ConditionalOnBean <-------");
return new ConfigBean();
}
三 . 自定义原理分析
3.1 Conditional 入口
对于 Configuration.@Bean 的创建方式 , Conditinal 的起点是 refush# invokeBeanFactoryPostProcessors , 在其中会调用 ConfigurationClassPostProcessor 进行处理
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 判断是否跳过
// 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 , 看一下主要的继承关系
去除不需要配置的类
第一步是快速去除不需要的类 , 主要流程如下 :
- 起点 : AbstractApplicationContext # refresh # invokeBeanFactoryPostProcessors
- 处理 : ConfigurationClassPostProcessor # postProcessBeanDefinitionRegistry 处理主要逻辑
- 拦截 : ConfigurationClassFilter # filter
- 匹配 : FilteringSpringBootCondition # match
注意 , 这里是进行 match 匹配 ,目的是获取基于正在导入的Configuration类的AnnotationMetadata的AutoConfigurationEntry
4.1 Filter 拦截
Filter 拦截是在 ConfigurationClassFilter ,其中会对所有的 Conditional 进行拦截处理
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 方法
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 的作用
该对象用来记录自动化配置过程中条件匹配的详细信息及日志信息
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 为例 , 此处存在一定的关联关系 :
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 , 从而跳转到该逻辑
@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
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 类似 , 这里就展示一种
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 流程
猜你喜欢
- 2024-11-27 SpringBoot 核心注解
- 2024-11-27 「Spring Boot 源码研究 」- 自动化装配条件化配置Conditional剖析
- 2024-11-27 如何在生产中关闭Swagger-ui
- 2024-11-27 一文读懂SpringBoot自动配置
- 2024-11-27 分布式事务系统Seata的这些安保机制是否会让你更放心
- 2024-11-27 深入理解 Spring Boot 中的 @Conditional 系列注解
- 2024-11-27 还在curd吗?封装属于自己的Spring-Boot-Starter
- 2024-11-27 「SpringBoot」条件注解@Conditional
- 2024-11-27 学会这10种定时任务,有点飘了...
- 2024-11-27 手把手学springboot-自定义视图解析和自定义Starter