[SpringBoot源码分析二]:@Condition

2023年 9月 16日 63.7k 0

1. 背景介绍

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

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

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

2. @Conditional

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

...
public @interface Conditional {

   /**
    * 检查类
    */
   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 beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
         spec.getIgnoredTypes(), parameterizedContainers);

   // 类型的匹配
   for (String type : spec.getTypes()) {
      // 获得指定类型Bean的名称
      Collection 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 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 onClasses = getCandidates(metadata, ConditionalOnClass.class);
   if (onClasses != null) {
      // 对传入的类进行过滤,返回不能加载的类
      List 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 onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
   if (onMissingClasses != null) {
      // 对传入的类进行过滤,返回能加载的类
      List 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就会利用多线程去进行匹配

image.png
image.png

6. 其他注解

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

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

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论