conditioner(condi)
盘点 Spring : Conditional
一 . 前言这一篇来看一下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 自定义 Conditionalpublic class DefaultConditional implements Condition { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { logger("------> 进行 Conditional 判断 <-------"); return false; }}2.3 基础使用
@Bean@ConditionalOnBean(TestService.class)public ConfigBean getConfigBean() { logger("------> 开始加载 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 (thisnditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) { configClassppedBeanMethods.add(methodName); return; } if (configClassppedBeanMethodsntains(methodName)) { return; }}3.2 Conditional 判断的流程
ConditionEvaluator 是核心处理类 , 最终都会调用 shouldSkip 判断是否跳过
// C- ConditionEvaluatorpublic 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, thisntext.getClassLoader()); conditions.add(condition); } } // Step 4 : 排序 AnnotationAwareOrderComparatorrt(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(thisntext, 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, thisConfigurationMetadata); 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 , 从而跳转到该逻辑@Overridepublic 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 OnClasspublic 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(specssage().because(reason)); } matchMessage = specssage(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 流程声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送至邮件举报,一经查实,本站将立刻删除。转载务必注明出处:http://www.hixs.net/article/20240326/169625048063041.html