MySQL, Oracle, Linux, 软件架构及大数据技术知识分享平台

网站首页 > 精选文章 / 正文

盘点 Spring : Conditional

2024-11-27 14:54 huorong 精选文章 12 ℃ 0 评论

一 . 前言

这一篇来看一下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 流程

Tags:conditionalonexpression

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言