0202条件过滤-自动装配原理-springboot2.7.x系列

news2024/10/5 20:22:32

1前言

在springboot的自动装配过程中,执行完候选配置类导入后,会进行条件过滤。那么在讲解条件过滤前,我们先来了解springboot常用的条件注解,以及它们底层执行原理。

在Spring Boot中,条件(Condition)是一种机制,它允许您基于应用程序中的配置或环境来自动装配或排除bean或配置类。条件是在应用程序启动期间评估的,如果条件为真,则相应的bean或配置类将被创建或注册,否则将被忽略。

Spring Boot为我们提供了许多内置条件,例如@ConditionalOnClass、@ConditionalOnBean和@ConditionalOnProperty等,这些条件可以根据我们的需要进行自定义和组合。

使用条件注解可以提高应用程序的可移植性,让应用程序在不同的环境中以不同的方式运行。例如,在测试环境中,我们可以使用虚拟的依赖来代替实际依赖,以便进行快速测试。在生产环境中,我们可以启用安全功能以保护应用程序。

2 Condition

做为条件注解的顶级接口,代码如下:

@FunctionalInterface
public interface Condition {

   /**
    * 条件匹配,注册被注解的组件;否则不注册
    * @param context 条件上下文
    * @param metadata 被注解的类或方法的元数据
    */
   boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

看下它的继承关系如下图2-1所示:

在这里插入图片描述

其子类SpringBootConditon实现该方法,代码如下:

@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();
   }
   // 省略异常处理
}

	public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

也就是说具体的条件判断逻辑方法getMatchOutcome由其子类实现,那么下面我们就来看看不同的条件注解的判断逻辑。

2 OnClassCondition

OnClassCondition是实现Spring Boot @ConditionalOnClass、@ConditionalOnMissing注解的底层实现

2.1 @ConditionalOnClass

@ConditionalOnClass是一个Spring Boot提供的条件注解之一。当指定的类路径下有指定的类时,该注解修饰的配置类或Bean才会被加载。如果类不存在,则该配置类或Bean将不会被加载。

源代码2.1-1如下所示:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

   /**
    * 指定类路径下存在的类名
    */
   Class<?>[] value() default {};

   /**
    * 指定必需的类路径下类的字符串表示
    */
   String[] name() default {};

}

我们继续看下OnClassCondition#getMatchOutcome()方法,代码2.1-2如下所示:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
   ClassLoader classLoader = context.getClassLoader();
   ConditionMessage matchMessage = ConditionMessage.empty();
   // 获取ConditionalOnClass注解value和name对应的候选类
   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注解value和name对应的候选类
   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);
}
  • ConditionalOnClass和ConditionalOnMissingClass条件判断逻辑为同一个类的同一个方法;
  • 执行相似的判断逻辑
    • 获取对应注解value或者name的值(候选类);
    • 通过过滤候选类,判断过滤不符合条件的候选类;
    • 结果不为空,即条件不符合,返回没匹配上的结果;否则返回匹配上的结果。

获取候选类getCandidates()方法源代码2.1-3如下所示:

private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {
   MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
   if (attributes == null) {
      return null;
   }
   List<String> candidates = new ArrayList<>();
   addAll(candidates, attributes.get("value"));
   addAll(candidates, attributes.get("name"));
   return candidates;
}
  • 获取注解属性value和name的值

不符合条件候选类过滤filter()方法源代码2.1-4如下所示:

protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
      ClassLoader classLoader) {
   if (CollectionUtils.isEmpty(classNames)) {
      return Collections.emptyList();
   }
   List<String> matches = new ArrayList<>(classNames.size());
   for (String candidate : classNames) {
      if (classNameFilter.matches(candidate, classLoader)) {
        // 符合过滤条件,加入集合
         matches.add(candidate);
      }
   }
   return matches;
}

下面我们继续看下ClassNameFilter源代码2.1-5如下所示:

protected enum ClassNameFilter {

   // 存在匹配
   PRESENT {

      @Override
      public boolean matches(String className, ClassLoader classLoader) {
         return isPresent(className, classLoader);
      }

   },
   // 缺失匹配
   MISSING {

      @Override
      public boolean matches(String className, ClassLoader classLoader) {
         // 存在匹配取反
         return !isPresent(className, classLoader);
      }

   };

   abstract boolean matches(String className, ClassLoader classLoader);

   static boolean isPresent(String className, ClassLoader classLoader) {
      if (classLoader == null) {
         classLoader = ClassUtils.getDefaultClassLoader();
      }
      try {
         // 解析成功,表示存在;否则不存在
         resolve(className, classLoader);
         return true;
      }
      catch (Throwable ex) {
         return false;
      }
   }

}

判断是否存在isPresen()执行逻辑:

  • resove通过加载器解析类名;
  • 没抛出异常,说明类路径下该类存在;否则说明类不存在。

我们继续追踪resolve()源代码2.1-6如下所示:

protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
   if (classLoader != null) {
      return Class.forName(className, false, classLoader);
   }
   return Class.forName(className);
}

Class.forName眼熟的很,不解释(因为底层原理暂时没研究😄)

2.2 @ConditionalOnMissingClass

@ConditionalOnMissingClass是Spring Boot提供的一个条件注解之一,用于在指定的类不存在于classpath中时才装配bean或配置类。该注解可以用于避免由于某些依赖项缺失而导致应用程序无法启动的情况。

注解条件相同@Conditional(OnBeanCondition.class),底层源码执行同上,不在详述,不同的是在获取候选类时传递的注解和过滤时候的条件不同,其他相同。

3 OnBeanCondition

OnBeanCondition是实现Spring Boot @ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnSingleCandidate注解的底层实现。

3.1 @ConditionalOnBean

@ConditionalOnBean是Spring Boot提供的一个条件注解之一,用于在指定的bean存在于应用程序上下文中时才装配bean或配置类。该注解可以用于确保特定的bean已经被装配才能创建和装配其他bean。

看下OnBeanCondition#getMatchOutcome()方法源代码3.1-1如下所示:

@Override
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);
      // 判断是否有任意一个没匹配
      if (!matchResult.isAllMatched()) {
         return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
      }
      Set<String> allBeans = matchResult.getNamesOfAllMatches();
      // 判断匹配bean是否只有1个
      if (allBeans.size() == 1) {
         // 返回匹配上信息
         matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
      }
      else {
         // 匹配多个bean
         // 获取有@Primary标记的bean
         List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,
               spec.getStrategy() == SearchStrategy.ALL);
         // 判断有@Primary注解的beans是否为空
         if (primaryBeans.isEmpty()) {
            // 返回没匹配上信息
            return ConditionOutcome
               .noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
         }
         // 判断有@Primary注解的beans数量是否大于1
         if (primaryBeans.size() > 1) {
            // 返回没匹配上信息
            return ConditionOutcome
               .noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
         }
         // 匹配多个beans,但是只有1个@Primary bean ,返回匹配上信息
         matchMessage = spec.message(matchMessage)
            .found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
            .items(Style.QUOTE, allBeans);
      }
   }
   // 判断是否存在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()) {
         String reason = createOnMissingBeanNoMatchReason(matchResult);
         return ConditionOutcome.noMatch(spec.message().because(reason));
      }
      matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
   }
   return ConditionOutcome.match(matchMessage);
}

3.2 @ConditionalOnMissingBean

@ConditionalOnMissingBean是Spring Boot提供的一个条件注解之一,用于在指定的bean不存在于应用程序上下文中时才装配bean或配置类。该注解可以用于避免重复创建bean或覆盖应用程序上下文中的bean。

3.3 @ConditionalOnSingleCandidate

@ConditionalOnSingleCandidate是Spring Boot提供的一个条件注解之一,用于在应用程序上下文中只有一个候选bean时才启用bean或配置类。如果存在多个候选bean或没有候选bean,则不满足条件,bean或配置类将不会被启用。

该注解通常与@Autowired或@Qualifier注解结合使用,用于指定依赖注入的bean,并确保只有一个符合条件的候选bean存在。

例如,以下代码展示了如何使用@ConditionalOnSingleCandidate注解,确保在应用程序上下文中只有一个实现了DataSource接口的bean时才启用MyRepository bean:

@Configuration
public class MyConfig {
    @Bean
    @ConditionalOnSingleCandidate(DataSource.class)
    public MyRepository myRepository(DataSource dataSource) {
        // 配置MyRepository bean
    }
}

在这个例子中,@ConditionalOnSingleCandidate注解用于指定DataSource.class作为候选bean的类型。只有当应用程序上下文中存在唯一一个实现了DataSource接口的bean时,myRepository bean才会被启用。

注意,如果应用程序上下文中存在多个候选bean或没有候选bean,@ConditionalOnSingleCandidate条件将不会满足,myRepository bean将不会被启用。

@ConditionalOnSingleCandidate注解还可以与其他条件注解组合使用,以实现更复杂的条件判断。例如,可以将@ConditionalOnSingleCandidate与@ConditionalOnBean注解组合使用,以确保只有在特定bean存在且该bean是唯一候选bean时才启用bean或配置类。

4 OnPropertyCondition

4.1 简介

OnPropertyCondition是Spring Framework提供的一个条件注解之一,它用于在应用程序的配置文件中指定的属性满足条件时才启用bean或配置类。OnPropertyCondition是实现Spring Boot @ConditionalOnProperty注解的底层实现之一。

4.2 @ConditionalOnProperty

  • 简介

@ConditionalOnProperties是Spring Boot提供的一个条件注解之一,用于在应用程序的配置文件中指定的属性满足条件时才启用bean或配置类。它可以根据配置文件中的属性值来决定是否装配bean或配置类。

  • 匹配规则

使用@ConditionalOnProperties注解时,需要指定一个或多个属性键值对作为参数。当这些属性在应用程序的配置文件中存在,并且属性值与指定的值匹配时,条件被认为是满足的,bean或配置类将被启用。

  • 源码分析

@ConditionalOnProperty源代码分析

package org.springframework.boot.autoconfigure.condition;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

   /**
    * names的别名
    * @return the names
    */
   String[] value() default {};

   /**
    * 属性前缀
    * @return the prefix
    */
   String prefix() default "";

   /**
    * 属性key的名称,如果有前缀,key拼接规则:前缀.名称
    *
    * 使用-分隔名称
    * @return the names
    */
   String[] name() default {};

   /**
    * 期望的属性值
    * @return the expected value
    */
   String havingValue() default "";

   /**
    * 如果属性没配置是否要匹配,默认false
    * @return if the condition should match if the property is missing
    */
   boolean matchIfMissing() default false;

}

havingValue(期望值)与property vlaue(配置值)匹配规则表如下所示:

Having values
Property Value{@code havingValue=""}{@code havingValue="true"}{@code havingValue="false"}{@code havingValue="foo"}
{@code "true"}yesyesnono
{@code "false"}nonoyesno
{@code "foo"}yesnonoyes

说明:

  • matchIfMissing默认为false,匹配结果如上表所示;如果matcIfMissing为true,当配置项缺失或者如上表为yes的时候,结果为匹配,其他情况就是不匹配。

下面我们通过分析OnPropertyCondition源码看看为什么有上面的匹配规则,源码如下4.2-1所示:

// 省略
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {

   @Override
   public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
     // 获取注解属性
      List<AnnotationAttributes> allAnnotationAttributes = metadata.getAnnotations()
         .stream(ConditionalOnProperty.class.getName())
         .filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes))
         .map(MergedAnnotation::asAnnotationAttributes)
         .collect(Collectors.toList());
      List<ConditionMessage> noMatch = new ArrayList<>();
      List<ConditionMessage> match = new ArrayList<>();
      for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
        // 根据环境变量中配置信息确定是否匹配
         ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
         (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
      }
      // 如果不匹配集合非空,返回不匹配结果
      if (!noMatch.isEmpty()) {
         return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
      }
      // 返回匹配结果
      return ConditionOutcome.match(ConditionMessage.of(match));
   }

   private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
      // 创建规格对象
      Spec spec = new Spec(annotationAttributes);
      List<String> missingProperties = new ArrayList<>();
      List<String> nonMatchingProperties = new ArrayList<>();
     // 搜集属性
      spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
      // 如果缺死属性集合不为空,返回未找到信息
      if (!missingProperties.isEmpty()) {
         return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
            .didNotFind("property", "properties")
            .items(Style.QUOTE, missingProperties));
      }
     // 如果不匹配属性集合不为空,返回不匹配信息
      if (!nonMatchingProperties.isEmpty()) {
         return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
            .found("different value in property", "different value in properties")
            .items(Style.QUOTE, nonMatchingProperties));
      }
     // 返回匹配信息
      return ConditionOutcome
         .match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
   }

   private static class Spec {

     // 前缀
      private final String prefix;

     // 期望值
      private final String havingValue;

     // 名称
      private final String[] names;

     // 如果丢失是否匹配
      private final boolean matchIfMissing;

      Spec(AnnotationAttributes annotationAttributes) {
         String prefix = annotationAttributes.getString("prefix").trim();
         if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
            prefix = prefix + ".";
         }
         this.prefix = prefix;
         this.havingValue = annotationAttributes.getString("havingValue");
         this.names = getNames(annotationAttributes);
         this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
      }
      // 获取注解中value或者name属性
      private String[] getNames(Map<String, Object> annotationAttributes) {
         String[] value = (String[]) annotationAttributes.get("value");
         String[] name = (String[]) annotationAttributes.get("name");
         Assert.state(value.length > 0 || name.length > 0,
               "The name or value attribute of @ConditionalOnProperty must be specified");
         Assert.state(value.length == 0 || name.length == 0,
               "The name and value attributes of @ConditionalOnProperty are exclusive");
         return (value.length > 0) ? value : name;
      }

      // 搜集属性值
      private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
         // 遍历注解属性名称
         for (String name : this.names) {
            // 拼接prefix组成配置key
            String key = this.prefix + name;
            // 判断配置项中算法包含该key
            if (resolver.containsProperty(key)) {
               // 如果获取key对应的值与期望值不匹配
               if (!isMatch(resolver.getProperty(key), this.havingValue)) {
                 // 加入不匹配集合
                  nonMatching.add(name);
               }
            }
            else {
               // 判断如果matchIfMissing为false
               if (!this.matchIfMissing) {
                  // 加入丢失集合
                  missing.add(name);
               }
            }
         }
      }
      // 是否匹配
      private boolean isMatch(String value, String requiredValue) {
         if (StringUtils.hasLength(requiredValue)) {
            return requiredValue.equalsIgnoreCase(value);
         }
         return !"false".equalsIgnoreCase(value);
      }
	// 省略

   }

}
  • 示例

例如,以下代码展示了如何使用@ConditionalOnProperties注解,仅当配置文件中的属性app.feature.enabled值为true时,才启用MyFeature类:

@Configuration
@ConditionalOnProperties("app.feature.enabled=true")
public class MyConfig {
    @Bean
    public MyFeature myFeature() {
        // 配置MyFeature bean
    }
}

在这个例子中,@ConditionalOnProperties注解的参数指定了属性键值对,即app.feature.enabled=true。只有当应用程序的配置文件中存在名为app.feature.enabled的属性,并且其值为true时,MyConfig配置类才会被启用。

@ConditionalOnProperties注解还支持指定多个属性键值对,以及使用逻辑运算符(例如AND、OR)来组合条件。例如:

@Configuration
@ConditionalOnProperties({
    "app.feature.enabled=true",
    "app.feature.someProperty=abc"
})
public class MyConfig {
    // ...
}

在这个例子中,只有当配置文件中同时存在app.feature.enabled=trueapp.feature.someProperty=abc这两个属性时,MyConfig配置类才会被启用。

需要注意的是,如果配置文件中没有指定的属性,或者属性值与指定的值不匹配,@ConditionalOnProperties条件将不会满足,相应的bean或配置类将不会被启用。

5 OnJavaCondition

  • 简介

OnJavaCondition是Spring Framework提供的一个条件注解之一,它用于在JVM java版本满足注解指定的java版本范围时才启用bean或配置类。OnJavaCondition是实现Spring Boot @ConditionalOnJava注解的底层实现之一。

5.1 @ConditionalOnJava

  • 源代码5.1-1如下所示

  • package org.springframework.boot.autoconfigure.condition;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.boot.system.JavaVersion;
    import org.springframework.context.annotation.Conditional;
    
    /**
     * 当前JVM java版本是否匹配指定的java版本范围
     */
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnJavaCondition.class)
    public @interface ConditionalOnJava {
    
       /**
        * java版本范围,默认大于等于
        */
       Range range() default Range.EQUAL_OR_NEWER;
    
       /**
        * java版本
        */
       JavaVersion value();
    
       /**
        * java版本范围枚举
        */
       enum Range {
    
          /**
           * 大于等于
           */
          EQUAL_OR_NEWER,
    
          /**
           * 小于
           */
          OLDER_THAN
    
       }
    
    }
    
  • 匹配规则

使用@ConditionalOnJava注解时,需要指定范围和java版本。当前JVM java版本在知道的范围时时,条件被认为是满足的,bean或配置类将被启用。

  • 示例
@Component
@ConditionalOnJava(range = EQUAL_OR_NEWER, value = JavaVersion.EIGHT)
public class UserService {
 // 省略
}

JVM java版本大于等于1.8时,当前bean才会被启用。

5.2 源码解析

OnJavaCondition源代码如下5.2-1所示

package org.springframework.boot.autoconfigure.condition;

import java.util.Map;

import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.Range;
import org.springframework.boot.system.JavaVersion;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnJavaCondition extends SpringBootCondition {

   // 获取当前JVM java版本
   private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion();

   @Override
   public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
     // 获取ConditionalOnJava注解属性
      Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
     // 获取注解指定范围
      Range range = (Range) attributes.get("range");
     // 获取注解指定java版本
      JavaVersion version = (JavaVersion) attributes.get("value");
     // 判断是否匹配
      return getMatchOutcome(range, JVM_VERSION, version);
   }
  // 判断是否匹配
   protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion, JavaVersion version) {
     // 判断是否匹配 
     boolean match = isWithin(runningVersion, range, version);
      String expected = String.format((range != Range.EQUAL_OR_NEWER) ? "(older than %s)" : "(%s or newer)", version);
      ConditionMessage message = ConditionMessage.forCondition(ConditionalOnJava.class, expected)
         .foundExactly(runningVersion);
      return new ConditionOutcome(match, message);
   }

   /**
    * 判断当前java版本是否在指定的java版本范围内
    */
   private boolean isWithin(JavaVersion runningVersion, Range range, JavaVersion version) {
     // 判断范围是否为大于等于
      if (range == Range.EQUAL_OR_NEWER) {
         return runningVersion.isEqualOrNewerThan(version);
      }
     // 判断范围是否为小于
      if (range == Range.OLDER_THAN) {
         return runningVersion.isOlderThan(version);
      }
      throw new IllegalStateException("Unknown range " + range);
   }

}

判断逻辑我们需要看下JavaVersion源代码如下5.2-2所示:

package org.springframework.boot.system;

import java.io.Console;
import java.lang.invoke.MethodHandles;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Future;
import java.util.stream.Stream;

import org.springframework.util.ClassUtils;

/**
 * Known Java versions.
 *
 * @author Oliver Gierke
 * @author Phillip Webb
 * @since 2.0.0
 */
public enum JavaVersion {

   /**
    * Java 1.8.
    */
   EIGHT("1.8", Optional.class, "empty"),

   /**
    * Java 9.
    */
   NINE("9", Optional.class, "stream"),

   /**
    * Java 10.
    */
   TEN("10", Optional.class, "orElseThrow"),

   /**
    * Java 11.
    */
   ELEVEN("11", String.class, "strip"),

   /**
    * Java 12.
    */
   TWELVE("12", String.class, "describeConstable"),

   /**
    * Java 13.
    */
   THIRTEEN("13", String.class, "stripIndent"),

   /**
    * Java 14.
    */
   FOURTEEN("14", MethodHandles.Lookup.class, "hasFullPrivilegeAccess"),

   /**
    * Java 15.
    */
   FIFTEEN("15", CharSequence.class, "isEmpty"),

   /**
    * Java 16.
    */
   SIXTEEN("16", Stream.class, "toList"),

   /**
    * Java 17.
    */
   SEVENTEEN("17", Console.class, "charset"),

   /**
    * Java 18.
    */
   EIGHTEEN("18", Duration.class, "isPositive"),

   /**
    * Java 19.
    */
   NINETEEN("19", Future.class, "state");

   private final String name;

   private final boolean available;

   JavaVersion(String name, Class<?> clazz, String methodName) {
      this.name = name;
      // 通过加载版本对应的类,判断是否有java版本对应的特定方法,确定java版本是否可用
      this.available = ClassUtils.hasMethod(clazz, methodName);
   }

   @Override
   public String toString() {
      return this.name;
   }

   /**
    * 获取当前java版本
    */
   public static JavaVersion getJavaVersion() {
     // 获取所有java枚举版本
      List<JavaVersion> candidates = Arrays.asList(JavaVersion.values());
     // 逆序排列,默认时java版本升序排列
      Collections.reverse(candidates);
      for (JavaVersion candidate : candidates) {
        // 返回第一个可用java版本
         if (candidate.available) {
            return candidate;
         }
      }
      return EIGHT;
   }

   /**
    * 是否大于等于给定版本
    */
   public boolean isEqualOrNewerThan(JavaVersion version) {
      return compareTo(version) >= 0;
   }

   /**
    * 是否小于给定的版本
    */
   public boolean isOlderThan(JavaVersion version) {
      return compareTo(version) < 0;
   }

}

判断逻辑为:判断指定类是否有版本特有的方法(通过遍历类的方法,通过方法名equals判断),来决定是否是对应版本。

思考:

  1. 为什么要通过上述方式获取java版本?
  2. 有没有更简单的方式呢?比如通过设置唯一值判断java版本?

6 OnWebApplicationCondition

  • 简介

OnWebApplicationCondition是Spring Framework提供的一个条件注解之一,它用于在应用是否为web应用或者指定web类型应用时才启用bean或配置类。OnWebApplicationCondition是实现Spring Boot @ConditionalOnWebApplication注解的底层实现之一。

6.1 @ConditionalOnWebApplication

  • ConditionalOnWebApplication源代码如下6.1-1所示:

  • package org.springframework.boot.autoconfigure.condition;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.context.annotation.Conditional;
    
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnWebApplicationCondition.class)
    public @interface ConditionalOnWebApplication {
    
       /**
        * web应用类型,默认任意
        */
       Type type() default Type.ANY;
    
       /**
        * web应用类型
        */
       enum Type {
    
          /**
           * 任意web应用类型
           */
          ANY,
    
          /**
           * sevlet web应用
           */
          SERVLET,
    
          /**
           * reactive web应用
           */
          REACTIVE
    
       }
    
    }
    
  • 匹配规则

    • Type.ANY:当前应用时sevlet或者reactive web应用匹配;
    • Type.SERVLET:当前为servlet web应用时匹配;
    • Type.REACTIVE:当前为reactive web应用时匹配。

6.2 源码解析

OnWebApplicationCondition源代码如下6.2-1所示:

package org.springframework.boot.autoconfigure.condition;

import java.util.Map;

import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebEnvironment;
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.ConfigurableWebEnvironment;
import org.springframework.web.context.WebApplicationContext;

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {

  // 运行servlet 应用加载的类 
  private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";

    // 运行reactive 应用加载的类
   private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult";

   @Override
   protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
         AutoConfigurationMetadata autoConfigurationMetadata) {
      ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
      for (int i = 0; i < outcomes.length; i++) {
         String autoConfigurationClass = autoConfigurationClasses[i];
         if (autoConfigurationClass != null) {
            outcomes[i] = getOutcome(
                  autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));
         }
      }
      return outcomes;
   }

   private ConditionOutcome getOutcome(String type) {
      if (type == null) {
         return null;
      }
      ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class);
      if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {
         if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
            return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
         }
      }
      if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) {
         if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
            return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());
         }
      }
      if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())
            && !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
         return ConditionOutcome.noMatch(message.didNotFind("reactive or servlet web application classes").atAll());
      }
      return null;
   }

   @Override
   public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
     // 是否有ConditionalOnWebApplication注解
      boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
     // 是否是web应用
      ConditionOutcome outcome = isWebApplication(context, metadata, required);
     // 判断是否有ConditionalOnWebApplication注解且不匹配
      if (required && !outcome.isMatch()) {
         return ConditionOutcome.noMatch(outcome.getConditionMessage());
      }
     // 判断没有ConditionalOnWebApplication注解且匹配
      if (!required && outcome.isMatch()) {
         return ConditionOutcome.noMatch(outcome.getConditionMessage());
      }
     // 返回匹配结果
      return ConditionOutcome.match(outcome.getConditionMessage());
   }

   private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata,
         boolean required) {
      switch (deduceType(metadata)) {
         case SERVLET:
            return isServletWebApplication(context);
         case REACTIVE:
            return isReactiveWebApplication(context);
         default:
            return isAnyWebApplication(context, required);
      }
   }

   private ConditionOutcome isAnyWebApplication(ConditionContext context, boolean required) {
      ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class,
            required ? "(required)" : "");
      ConditionOutcome servletOutcome = isServletWebApplication(context);
      if (servletOutcome.isMatch() && required) {
         return new ConditionOutcome(servletOutcome.isMatch(), message.because(servletOutcome.getMessage()));
      }
      ConditionOutcome reactiveOutcome = isReactiveWebApplication(context);
      if (reactiveOutcome.isMatch() && required) {
         return new ConditionOutcome(reactiveOutcome.isMatch(), message.because(reactiveOutcome.getMessage()));
      }
      return new ConditionOutcome(servletOutcome.isMatch() || reactiveOutcome.isMatch(),
            message.because(servletOutcome.getMessage()).append("and").append(reactiveOutcome.getMessage()));
   }

  // 是否是servlet web应用
   private ConditionOutcome isServletWebApplication(ConditionContext context) {
      ConditionMessage.Builder message = ConditionMessage.forCondition("");
     // 是否加载指定类
      if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) {
         return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
      }
     // 通过条件上下文获取beanFactory如果不为空
      if (context.getBeanFactory() != null) {
        // 获取作用域
         String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
        // 判断作用域包含session
         if (ObjectUtils.containsElement(scopes, "session")) {
            return ConditionOutcome.match(message.foundExactly("'session' scope"));
         }
      }
     // 通过条件上下文获取环境变量对象,判断是否是ConfigurableWebEnvironment实例
      if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
         return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));
      }
     // 通过条件上下文获取资源加载器对象,判断是否是WebApplicationContext实例
      if (context.getResourceLoader() instanceof WebApplicationContext) {
         return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
      }
     // 否则返回不匹配结果
      return ConditionOutcome.noMatch(message.because("not a servlet web application"));
   }
 
   // 是否是reactive web应用
   private ConditionOutcome isReactiveWebApplication(ConditionContext context) {
      ConditionMessage.Builder message = ConditionMessage.forCondition("");
      if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, context.getClassLoader())) {
         return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());
      }
      if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) {
         return ConditionOutcome.match(message.foundExactly("ConfigurableReactiveWebEnvironment"));
      }
      if (context.getResourceLoader() instanceof ReactiveWebApplicationContext) {
         return ConditionOutcome.match(message.foundExactly("ReactiveWebApplicationContext"));
      }
      return ConditionOutcome.noMatch(message.because("not a reactive web application"));
   }

  // 获取ConditionalOnWebApplication注解type属性值
   private Type deduceType(AnnotatedTypeMetadata metadata) {
      Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
      if (attributes != null) {
         return (Type) attributes.get("type");
      }
      return Type.ANY;
   }

}

判断逻辑:判断是否加载指定的类或者上下文满足特定条件。

结语

如果小伙伴什么问题或者指教,欢迎交流。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/springboot-custom

参考:

[1]Springboot视频教程[CP/OL].P14-18.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/575472.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用qemu模拟CXL.mem设备

CXL可以说是自PCIe技术诞生几十年以来最具变革性的新技术了。可以想象有了CXL以后机箱的边界将被彻底打破&#xff0c;服务器互相使用对方的内存&#xff0c;网卡&#xff0c;GPU 。整个机架甚至跨机架的超级资源池化成为可能&#xff0c;云计算也将进入一个新的时代。 当前In…

C++寄存器优化

在C里面有个有趣的现象&#xff0c;先看代码 #include<iostream> using namespace std; int main() {int const tmp 100; //定义常量tmp tmp不能修改int const* p &tmp; //不能通过指针修改指向的值 int* const q const_cast<int*>(p); //去常属性 可以通过…

【C++11】C++11新增语法特性 右值引用/移动语义/完美转发

C11 右值引用 1 右值引用1.1 左值 、 右值1.2 左值引用 VS 右值引用1.3 谈谈C11引入右值引用的意义1.4 左值引用和右值引用的一些细节问题 2 移动语义3 完美转发4 总结 1 右值引用 1.1 左值 、 右值 在C中所有的值不是左值就是右值。左值是指表达式结束后依然存在的持久化对象…

【JavaSE】Java基础语法(二十二):包装类

文章目录 1. 基本类型包装类2. Integer类3. 自动拆箱和自动装箱4. int和String类型的相互转换 1. 基本类型包装类 基本类型包装类的作用 将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据常用的操作之一&#xff1a;用于基本数据类型与字符串之间的…

Goby 漏洞更新|锐捷网络 NBR路由器 webgl.data 信息泄露漏洞

漏洞名称&#xff1a;锐捷网络 NBR路由器 webgl.data 信息泄露漏洞 English Name&#xff1a;Ruijie NBR Router webgl.data information CVSS core: 7.5 影响资产数&#xff1a;204290 漏洞描述&#xff1a; 锐捷网络NBR700G路由器是锐捷网络股份有限公司的一款无线路由设…

口碑超好的挂耳式耳机盘点,这几款蓝牙耳机值得一看!

运动已成为人们业余生活中不可缺少的组成部分&#xff0c;徒步、夜跑、骑行等运动项目受到越来越多的人的喜欢&#xff0c;运动与耳机的搭配也是当代年轻人喜爱的行为方式&#xff0c;在颠簸的运动项目中耳机的稳固性和舒适性是非常主要的&#xff0c;现在新推出的开放式耳机深…

Ps:移除工具

移除工具 Remove Tool是一款简单易用、功能强大的工具&#xff0c;可快速、轻松地清去除图片中的干扰元素或不需要的区域。 快捷键&#xff1a;J 就如同使用画笔工具一样&#xff0c;只要在不需要的对象上涂抹&#xff08;描边&#xff09;即可将其去除。 移动工具基于人工智能…

几号发工资就能看出公司的好坏?(文末附招聘岗位)

作为一名资深的职场搬砖人&#xff0c;不知道各位最近有没有跟我一样关注到这个话题 ​#发工资时间看公司#小编刚看到这个话题的第一印象&#xff0c;想的是发工资时间无非是公司实力的体现&#xff0c;工资发的越早证明这个公司的现金流越稳定强大。 打开评论区&#xff0c;不…

Linux——进程优先级

1.什么是优先级&#xff1f; 优先级和权限息息相关。权限的含义为能还是不能做这件事。而优先级则表示&#xff1a;你有权限去做&#xff0c;只不过是先去做还是后去做这件事罢了。 2.为什么会存在优先级&#xff1f; 优先级表明了狼多肉少的理念&#xff0c;举个例子&#xff…

Processing通过编程实现艺术设计_实现艺术和现实的交互---数据设计分析002

还记得这个生命的游戏,也是在这上面有 https://processing.org/ 官网是这个 使用Processing可以用编程的方式来创作艺术 Processing是一门开源编程语言,可以直接对用它来做艺术创作, 可以看一些它的作品 https://processing.org/examples/gameoflife.html 官网是这个,完…

作为C/C++程序员你可以不使用但你必须会的Linux调试器-gdb(GNU Debugger)

gdb(GNU Debugger) 是一个用于调试 Linux 系统的软件工具。在学习 Linux 的过程中&#xff0c;gdb 的重要性不言而喻。以下是 gdb 在 Linux 学习者中的重要性的详细说明: 帮助理解 Linux 系统的运作方式:gdb 是一个强大的调试工具&#xff0c;可以帮助学习者深入了解 Linux 系统…

代码随想录算法训练营day53 | 1143.最长公共子序列,1035.不相交的线,53. 最大子序和 动态规划

代码随想录算法训练营day53 | 1143.最长公共子序列&#xff0c;1035.不相交的线&#xff0c;53. 最大子序和 动态规划 1143.最长公共子序列解法一&#xff1a;动态规划 1035.不相交的线解法一&#xff1a;动态规划 53. 最大子序和 动态规划解法一&#xff1a;动态规划解法二&am…

Python学习38:凯撒密码——解密

类型&#xff1a;字符串‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ 描述‪‬‪…

【Netty】Netty 编码器(十三)

文章目录 前言一、MessageToByteEncoder 抽象类二、MessageToMessageEncoder 抽象类总结 前言 回顾Netty系列文章&#xff1a; Netty 概述&#xff08;一&#xff09;Netty 架构设计&#xff08;二&#xff09;Netty Channel 概述&#xff08;三&#xff09;Netty ChannelHan…

notepad++查询指定内容并复制

背景说明 记录一下使用notepad进行文本内容查找以及替换的相关场景,简单记录方便后期查看,场景内容: 1.从指定的给出内容中筛选出所有的人员id集合 2.将每一行后面添加逗号 1.从指定的给出内容中筛选出所有的人员id集合 要求从指定的给出内容中筛选出所有的人员id集…

便携式明渠流量计的使用特点

便携式明渠流量计使用特点&#xff1a; 便携式明渠流量计使用特点&#xff0c;首先了解相关要求&#xff1a; 随着新标准JJG711-1990《明渠堰槽流量计试行检定规程》、HJ/T15-2019《超声波明渠污水流量计技术要求及检测方法》、HJ 354-2019《水污染源在线监测系统(CODCr、NH3-N…

Java开发 - 你不知道的JVM优化详解

前言 代码上的优化达到一定程度&#xff0c;再想提高系统的性能就很难了&#xff0c;这时候&#xff0c;优秀的程序猿往往会从JVM入手来进行系统的优化。但话说回来&#xff0c;JVM方面的优化也是比较危险的&#xff0c;如果单单从测试服务器来优化JVM是没有太大的意义的&…

制作PE工具盘

文章目录 前言一、什么是PE工具箱&#xff1f;二、制作WinPE工具箱例&#xff1a;制作ISO模式的PE工具箱 三、PE工具箱的典型应用1.清除Windows登陆密码2.调整分区大小3.系统备份4.系统恢复例&#xff1a;系统备份与恢复 四、使用U深度制作的PE工具恢复误删除的文件实验步骤注意…

springboot+vue车辆充电桩管理系统(java项目源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的车辆充电桩管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;…

Vim的使用

1.什么是Vim Vim是Linux下的一款代码编辑器&#xff0c;vi/vim的区别简单点来说&#xff0c;它们都是多模式编辑器&#xff0c;不同的是vim是vi的升级版本&#xff0c;它不仅兼容vi的所有指令&#xff0c;而且还有一些新的特性在里面。例如语法加亮&#xff0c;可视化操作不仅…