likes
comments
collection
share

[SpringBoot源码分析二]:@Condition

作者站长头像
站长
· 阅读数 25

1. 背景介绍

在整个SpringBoot项目中关于Bean的注册,我们可能需要指示Bean只有在所有条件满足的情况下才有资格注册到容器中,比如说像下面这个例子,如果说我们已经注册过ViewResolver自然不需要SpringMVC帮我们注册了 [SpringBoot源码分析二]:@Condition

上面的ConditionalOnBean正是Conditional的一种延伸

本文将围绕着SpringBoot中如何利用Conditional及其子注解进行条件判断来讲解

2. @Conditional

我们先看看这个注解的样子,发现这个接口其实就是传入一个Condition类作为检查类

...
public @interface Conditional {

   /**
    * 检查类
    */
   Class<? extends Condition>[] value();

}

Condition也很简单,就只有一个matches方法,根据返回值判断是否匹配成功

@FunctionalInterface
public interface Condition {

   boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

@ConditionalCondition长的非常像,事实上呢这两也是一一对应的,比如说@ConditionalOnBean对应的就是OnBeanCondition

[SpringBoot源码分析二]:@Condition

SpringBoot中支持的@Conditional注解非常的多,接下来我会逐一讲解下面的注解

[SpringBoot源码分析二]:@Condition

3. ConditionEvaluator

这里为什么要介绍这个类呢,前面我只是介绍了这些注解的作用以及对应的检查类,但是SpringBoot是如何判断一个Bean需要执行检查的呢,又是怎么判断需要通过哪个检查类去检查呢? 没错靠ConditionEvaluator

紧接着我们看ConditionEvaluator是在哪里执行的,我这里只举一个例子,当我们的候选配置类都被包装为ConfigurationClass然后转为BeanDefinitions的时候会执行loadBeanDefinitionsForConfigurationClass(...)方法, 大家可以看到这里首当其冲的进行了检查

[SpringBoot源码分析二]:@Condition

大家这里不用纠结什么ConfigurationClassBeanDefinitions,只需要记住Bean在生命周期会通过ConditionEvaluator去确定Bean需要检查什么类型

接着我们看ConditionEvaluator的核心方法shouldSkip(...)

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
   // 没有携带 Conditional 的注解不需要跳过
   if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
      return false;
   }

   if (phase == null) {
      //如果是注解元数据,并且标志了某些注解
      if (metadata instanceof AnnotationMetadata &&
            ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
         //再递归调用:看是否需要跳过
         return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
      }
      //再递归调用:REGISTER_BEAN:表示无论条件是否成立都加入
      return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
   }

   List<Condition> conditions = new ArrayList<>();
   //获取当前类上有关@Conditional注解的信息
   //是一个List<String[]>,list表示可能有多个@Conditional注解
   for (String[] conditionClasses : getConditionClasses(metadata)) {
      //其中String[]是因为@Conditional是可以传入多个Class,其他的衍生类,就不行了,指定了一个了
      for (String conditionClass : conditionClasses) {
         //将当前类所标志的@Conditional类型的注解实例化,加入候选集合中
         Condition condition = getCondition(conditionClass, this.context.getClassLoader());
         conditions.add(condition);
      }
   }

   //是利用AnnotationAwareOrderComparator进行排序,
   //这是一个针对Ordered接口、PriorityOrdered接口和@Order注解进行排序的
   AnnotationAwareOrderComparator.sort(conditions);

   //遍历所有的@Conditional注解,看能否满足条件
   for (Condition condition : conditions) {
      ConfigurationPhase requiredPhase = null;
      if (condition instanceof ConfigurationCondition) {
         requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
      }
      //condition.matches方法进行匹配
      if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
         return true;
      }
   }

   return false;
}

虽然说这个方法很长,但是当我精简下,大家就明白了:

  • 如若元数据没有带有@Conditional及其子注解就直接返回
  • 一旦发现了,就把上面带有的检查类拿出来,进行实例化 [SpringBoot源码分析二]:@Condition
  • 将所有检查类进行排序,其排序逻辑正是检查类上带有的@Order注解
  • 然后调用检测类的matches(...)方法进行检查,如果有任何一个检查类匹配失败,则认为需要跳过

这里对部分@Condition子注解和对应的检查类做一个归类,方便大家理解

  • @ConditionalOnBeanOnBeanCondition
  • @ConditionalOnClassOnClassCondition
  • @ConditionalOnWebApplicationOnWebApplicationCondition
  • ....

4. @ConditionalOnBean

@ConditionalOnBean:要求容器中必须存在某种Bean,其检查类是OnBeanCondition

在介绍OnBeanCondition之前呢,我们得先了解下这个类的结构 [SpringBoot源码分析二]:@Condition

我们可以发现它实现了两个重要的接口

  • SpringBootCondition
  • FilteringSpringBootCondition

我们先看SpringBootCondition,它实现了Condition接口,本质上就是基类,定义了matches(...)方法,最终的匹配结果其实是getMatchOutcome(...)方法决定的

public abstract class SpringBootCondition implements Condition {
   
   ...
   @Override
   public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      // 获取全路径(包名 + 类名)
      String classOrMethodName = getClassOrMethodName(metadata);
      try {
         // 获得匹配结果
         ConditionOutcome outcome = getMatchOutcome(context, metadata);
         logOutcome(classOrMethodName, outcome);
         // 记录条件评估的发生情况
         recordEvaluation(context, classOrMethodName, outcome);
         // 返回匹配结果
         return outcome.isMatch();
      }
      catch (NoClassDefFoundError ex) {
        ...
      }
      catch (RuntimeException ex) {
         throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
      }
   }
   ...
}

紧接着我们看FilteringSpringBootCondition类,大家细心的话就会发现这个类还实现了AutoConfigurationImportFilter接口,大家看名字就知道这是一个关于自动配置类的匹配类

abstract class FilteringSpringBootCondition extends SpringBootCondition
      implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
   ...
   /**
    * 返回自动配置类评估结果
    * @param autoConfigurationClasses 自动配置候选类
    * @param autoConfigurationMetadata 自动配置类规则
    * @return
    */
   @Override
   public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
      ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
      // 获得评估结果, 某个元素为空代表匹配成功
      ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
      boolean[] match = new boolean[outcomes.length];
      for (int i = 0; i < outcomes.length; i++) {
         match[i] = (outcomes[i] == null || outcomes[i].isMatch());
         // 匹配失败的情况
         if (!match[i] && outcomes[i] != null) {
            logOutcome(autoConfigurationClasses[i], outcomes[i]);
            if (report != null) {
               // 记录条件评估的发生情况
               report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
            }
         }
      }
      return match;
   }
}

至于为什么要多定义一个match方法呢,我理解如下:

  • 方法的入参不一样
  • 由于spring.factoriesspring-autoconfigure-metadata.properties文件中可能存在多个自动配置类以及规则,那么返回的类型肯定需要是一个数组或者集合了

刚才我说了matches(...)的匹配结果是靠getMatchOutcome(...)方法决定的,所以我们直接看这个方法在OnBeanCondition中的实现

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
   ConditionMessage matchMessage = ConditionMessage.empty();
   MergedAnnotations annotations = metadata.getAnnotations();
   //  检查 ConditionalOnBean 情况
   if (annotations.isPresent(ConditionalOnBean.class)) {
      Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
      // 返回匹配结果
      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());
   }

   //  检查 ConditionalOnSingleCandidate 情况
   if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
      Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
      // 返回匹配结果
      MatchResult matchResult = getMatchingBeans(context, spec);
      // 先判断是否有此Bean
      if (!matchResult.isAllMatched()) {
         return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
      }
      // 再看是否只存在一个候选Bean,或者只有一个主要候选Bean
      else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
            spec.getStrategy() == SearchStrategy.ALL)) {
         return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
               .items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
      }
      // 到这就说明条件成立,封装返回消息
      matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE,
            matchResult.getNamesOfAllMatches());
   }

   // 检查 ConditionalOnMissingBean 情况
   if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
      Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
            ConditionalOnMissingBean.class);
      // 返回匹配结果
      MatchResult matchResult = getMatchingBeans(context, spec);
      // 如果匹配成功,在这个注解的情况下,表示不能加入容器中
      if (matchResult.isAnyMatched()) {
         // 创建的 @OnMissingBean 的无法匹配结果
         String reason = createOnMissingBeanNoMatchReason(matchResult);
         // 返回不匹配的信息
         return ConditionOutcome.noMatch(spec.message().because(reason));
      }
      matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
   }
   // 返回匹配成功的信息
   return ConditionOutcome.match(matchMessage);
}

虽然说这个方法看起来很长,其实很简单,由于OnBeanCondition这个检查类是支持多个注解的@ConditionalOnBean@ConditionalOnSingleCandidate@ConditionalOnMissingBean,所以这里封装为了Spec类,然就调用了getMatchingBeans(...)方法

然后我们看了getMatchingBeans(...)方法,其实就是针对Bean类型、Bean名称,注解进行匹配

protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
   ClassLoader classLoader = context.getClassLoader();
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
   Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
   // 是否在所有父容器中查找
   if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
      BeanFactory parent = beanFactory.getParentBeanFactory();
      Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
            "Unable to use SearchStrategy.ANCESTORS");
      beanFactory = (ConfigurableListableBeanFactory) parent;
   }
   MatchResult result = new MatchResult();
   // 获得所有可以忽略的Bean名称
   Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
         spec.getIgnoredTypes(), parameterizedContainers);

   // 类型的匹配
   for (String type : spec.getTypes()) {
      // 获得指定类型Bean的名称
      Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
            parameterizedContainers);
      // 移除需要忽略的
      typeMatches.removeAll(beansIgnoredByType);

      // 记录匹配不成功或者成功
      if (typeMatches.isEmpty()) {
         result.recordUnmatchedType(type);
      }
      else {
         result.recordMatchedType(type, typeMatches);
      }
   }

   // 注解的匹配,和类型的匹配差不多
   for (String annotation : spec.getAnnotations()) {
      Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
            considerHierarchy);
      annotationMatches.removeAll(beansIgnoredByType);
      if (annotationMatches.isEmpty()) {
         result.recordUnmatchedAnnotation(annotation);
      }
      else {
         result.recordMatchedAnnotation(annotation, annotationMatches);
      }
   }

   // 名称的匹配,和类型的匹配差不多
   for (String beanName : spec.getNames()) {
      if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
         result.recordMatchedName(beanName);
      }
      else {
         result.recordUnmatchedName(beanName);
      }
   }
   return result;
}

5. @ConditionalOnClass

@ConditionalOnClass:检查类是OnClassCondition,要求能够通过ClassLoader加载到指定的类

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
   ClassLoader classLoader = context.getClassLoader();
   ConditionMessage matchMessage = ConditionMessage.empty();

   // ConditionalOnClass的情况
   List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
   if (onClasses != null) {
      // 对传入的类进行过滤,返回不能加载的类
      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));
      }
      // 匹配成功
      matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
            .found("required class", "required classes")
            .items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
   }

   // ConditionalOnMissingClass的情况
   List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
   if (onMissingClasses != null) {
      // 对传入的类进行过滤,返回能加载的类
      List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
      // 匹配失败:有可以加载的类
      if (!present.isEmpty()) {
         return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
               .found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
      }
      // 匹配成功
      matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
            .didNotFind("unwanted class", "unwanted classes")
            .items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
   }
   return ConditionOutcome.match(matchMessage);
}

其实这个类没什么好讲的,但是这个类实现了FilteringSpringBootCondition, 这里针对于自动配置类的过滤,做了特殊的设置,那么紧接着就来看看

大家看我的注释就知道,这里可以通过两个线程进行检查自动配置类

protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
      AutoConfigurationMetadata autoConfigurationMetadata) {
   //如果系统内核大于1,就多开一个线程进行匹配
   if (Runtime.getRuntime().availableProcessors() > 1) {
      return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
   }
   //一个线程进行匹配
   else {
      OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
            autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
      return outcomesResolver.resolveOutcomes();
   }
}

resolveOutcomesThreaded(...)方法中会将自动配置类分为两部分,分别交给了StandardOutcomesResolverThreadedOutcomesResolver去处理

private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
      AutoConfigurationMetadata autoConfigurationMetadata) {
   // 确定第一个线程的结束位置和第二个线程的开始位置
   int split = autoConfigurationClasses.length / 2;
   // 创建一个多线程的匹配类
   OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
         autoConfigurationMetadata);
   // 创建第二个用当前线程的匹配类
   OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
         autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());

   // 开始匹配
   ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
   ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();

   // 封账匹配结果,并返回
   ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
   System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
   System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
   return outcomes;
}

StandardOutcomesResolver其实就很简单就for循环检查,而ThreadedOutcomesResolver就会利用多线程去进行匹配

[SpringBoot源码分析二]:@Condition [SpringBoot源码分析二]:@Condition

6. 其他注解

至于其他的注解其实不太常见,我这就就简单做个介绍

  • @OnExpressionCondition:支持以SpEL表达式去解析
  • @ConditionalOnJava:要求JVM版本范围的
  • @ConditionalOnWebApplication:要求当前Web程序类型,是Servlet还是Reactive
  • ...