Interview must ask: How is the underlying conditional annotation in SpringBoot implemented?

SpringBoot provides a unique annotation: Conditional Annotation. such as:

  • @ConditionalOnBean、
  • @ConditionalOnClass、
  • @ConditionalOnExpression、
  • @ConditionalOnMissingBean等。

The significance of conditional annotations lies in dynamic recognition (also can be said to be automatic code execution). For example, @ConditionalOnClass will check whether there is a corresponding class in the class loader. If so, the annotation-modified class is eligible to be registered by the Spring container, otherwise it will be skipped.

For example, the definition of the automatic configuration class FreemarkerAutoConfiguration is as follows:

@Configuration
@ConditionalOnClass({ freemarker.template.Configuration.class,
    FreeMarkerConfigurationFactory.class })@AutoConfigureAfter(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties(FreeMarkerProperties.class)
public class FreeMarkerAutoConfiguration

This automated configuration class is modified by the @ConditionalOnClass conditional annotation. The significance of this conditional annotation is to determine whether there are two classes of freemarker.template.Configuration and FreeMarkerConfigurationFactory in the class loader. If both exist, the FreeMarkerAutoConfiguration configuration will be loaded in the Spring container. Class; otherwise it will not be loaded.

Some basics inside conditional annotations

Before analyzing the underlying implementation of conditional annotations, let's take a look at the definition of these conditional annotations. Take the @ConditionalOnClass annotation as an example, its definition is as follows:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
  Class<?>[] value() default {}; // 需要匹配的类
  String[] name() default {}; // 需要匹配的类名
}

It has two attributes, namely, class array and string array (the same function, different types), and they are modified by the @Conditional annotation. This @Conditional annotation has a Class<? extends Condition>[] type named values Attributes. This Condition is an interface used to match whether the component is eligible to be registered by the container, defined as follows:

public interface Condition {
  // ConditionContext内部会存储Spring容器、应用程序环境信息、资源加载器、类加载器
  boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

That is to say, the @Conditional annotation attribute can hold multiple implementation classes of the Condition interface. All Condition interfaces need to be matched successfully before this @Conditional modified component is eligible to be registered.

The Condition interface has a sub-interface ConfigurationCondition:

public interface ConfigurationCondition extends Condition {
  ConfigurationPhase getConfigurationPhase();
  public static enum ConfigurationPhase {
    PARSE_CONFIGURATION,    REGISTER_BEAN  }}

This sub-interface is a special conditional interface with an additional getConfigurationPhase method, which is the effective phase of conditional annotations. It will only take effect under the two phases defined in ConfigurationPhase.

The Condition interface has an abstract class that implements SpringBootCondition, and all condition classes corresponding to condition annotations in SpringBoot inherit this abstract class. It implements the matches method:

@Override
public final boolean matches(ConditionContext context,    AnnotatedTypeMetadata metadata) {  String classOrMethodName = getClassOrMethodName(metadata); // 得到类名或者方法名(条件注解可以作用的类或者方法上)
  try {
    ConditionOutcome outcome = getMatchOutcome(context, metadata); // 抽象方法,具体子类实现。ConditionOutcome记录了匹配结果boolean和log信息
    logOutcome(classOrMethodName, outcome); // log记录一下匹配信息
    recordEvaluation(context, classOrMethodName, outcome); // 报告记录一下匹配信息
    return outcome.isMatch(); // 返回是否匹配
  }  catch (NoClassDefFoundError ex) {
    throw new IllegalStateException(
        "Could not evaluate condition on " + classOrMethodName + " due to "
            + ex.getMessage() + " not "
            + "found. Make sure your own configuration does not rely on "
            + "that class. This can also happen if you are "
            + "@ComponentScanning a springframework package (e.g. if you "
            + "put a @ComponentScan in the default package by mistake)",
        ex);  }  catch (RuntimeException ex) {
    throw new IllegalStateException(
        "Error processing condition on " + getName(metadata), ex);
  }}

Class-based conditional annotation

SpringBoot provides two class-based conditional annotations: @ConditionalOnClass (the specified class exists in the class loader) or @ConditionalOnMissingClass (the specified class does not exist in the class loader).

The condition class corresponding to @ConditionalOnClass or @ConditionalOnMissingClass annotation is OnClassCondition, which is defined as follows:

@Order(Ordered.HIGHEST_PRECEDENCE) // 优先级、最高级别
class OnClassCondition extends SpringBootCondition {  @Override  public ConditionOutcome getMatchOutcome(ConditionContext context,      AnnotatedTypeMetadata metadata) {    StringBuffer matchMessage = new StringBuffer(); // 记录匹配信息
    MultiValueMap<String, Object> onClasses = getAttributes(metadata,        ConditionalOnClass.class); // 得到@ConditionalOnClass注解的属性
    if (onClasses != null) { // 如果属性存在
      List<String> missing = getMatchingClasses(onClasses, MatchType.MISSING,          context); // 得到在类加载器中不存在的类
      if (!missing.isEmpty()) { // 如果存在类加载器中不存在对应的类,返回一个匹配失败的ConditionalOutcome
        return ConditionOutcome
            .noMatch("required @ConditionalOnClass classes not found: "
                + StringUtils.collectionToCommaDelimitedString(missing));      }                // 如果类加载器中存在对应的类的话,匹配信息进行记录      matchMessage.append("@ConditionalOnClass classes found: "
          + StringUtils.collectionToCommaDelimitedString(              getMatchingClasses(onClasses, MatchType.PRESENT, context)));    }        // 对@ConditionalOnMissingClass注解做相同的逻辑处理(说明@ConditionalOnClass和@ConditionalOnMissingClass可以一起使用)    MultiValueMap<String, Object> onMissingClasses = getAttributes(metadata,        ConditionalOnMissingClass.class);    if (onMissingClasses != null) {
      List<String> present = getMatchingClasses(onMissingClasses, MatchType.PRESENT,          context);      if (!present.isEmpty()) {
        return ConditionOutcome
            .noMatch("required @ConditionalOnMissing classes found: "
                + StringUtils.collectionToCommaDelimitedString(present));      }      matchMessage.append(matchMessage.length() == 0 ? "" : " ");
      matchMessage.append("@ConditionalOnMissing classes not found: "
          + StringUtils.collectionToCommaDelimitedString(getMatchingClasses(              onMissingClasses, MatchType.MISSING, context)));    }        // 返回全部匹配成功的ConditionalOutcome    return ConditionOutcome.match(matchMessage.toString());
  }  private enum MatchType { // 枚举:匹配类型。用于查询类名在对应的类加载器中是否存在。
    PRESENT { // 匹配成功
      @Override      public boolean matches(String className, ConditionContext context) {        return ClassUtils.isPresent(className, context.getClassLoader());
      }    },    MISSING { // 匹配不成功
      @Override      public boolean matches(String className, ConditionContext context) {        return !ClassUtils.isPresent(className, context.getClassLoader());
      }    };    public abstract boolean matches(String className, ConditionContext context);  }}

For example, the @ConditionalOnClass annotation in FreemarkerAutoConfiguration has value attributes that are freemarker.template.Configuration.class and FreeMarkerConfigurationFactory.class. The log message in the final ConditionalOutcome obtained during the execution of OnClassCondition is as follows:

1 @ConditionalOnClass classes found: freemarker.template.Configuration,org.springframework.ui.freemarker.FreeMarkerConfigurationFactory

Bean-based conditional annotation

@ConditionalOnBean (the specified bean exists in the Spring container), @ConditionalOnMissingBean (the specified bean does not exist in the Spring container), and ConditionalOnSingleCandidate (there is only one specified bean in the Spring container) are all based on Bean conditional annotations, and they correspond to The condition class is ConditionOnBean.

The @ConditionOnBean annotation is defined as follows:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
  Class<?>[] value() default {}; // 匹配的bean类型
  String[] type() default {}; // 匹配的bean类型的类名
  Class<? extends Annotation>[] annotation() default {}; // 匹配的bean注解
  String[] name() default {}; // 匹配的bean的名字
  SearchStrategy search() default SearchStrategy.ALL; // 搜索策略。提供CURRENT(只在当前容器中找)、PARENTS(只在所有的父容器中找;但是不包括当前容器)和ALL(CURRENT和PARENTS的组合)
}

The matching code of the OnBeanCondition conditional class is as follows:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,    AnnotatedTypeMetadata metadata) {  StringBuffer matchMessage = new StringBuffer(); // 记录匹配信息
  if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
    BeanSearchSpec spec = new BeanSearchSpec(context, metadata,        ConditionalOnBean.class); // 构造一个BeanSearchSpec,会从@ConditionalOnBean注解中获取属性,然后设置到BeanSearchSpec中
    List<String> matching = getMatchingBeans(context, spec); // 从BeanFactory中根据策略找出所有匹配的bean
    if (matching.isEmpty()) { // 如果没有匹配的bean,返回一个没有匹配成功的ConditionalOutcome
      return ConditionOutcome
          .noMatch("@ConditionalOnBean " + spec + " found no beans");
    }    // 如果找到匹配的bean,匹配信息进行记录    matchMessage.append(        "@ConditionalOnBean " + spec + " found the following " + matching);
  }  if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { // 相同的逻辑,针对@ConditionalOnSingleCandidate注解
    BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,        ConditionalOnSingleCandidate.class);    List<String> matching = getMatchingBeans(context, spec);    if (matching.isEmpty()) {
      return ConditionOutcome.noMatch(
          "@ConditionalOnSingleCandidate " + spec + " found no beans");
    }    else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matching)) { // 多了一层判断,判断是否只有一个bean
      return ConditionOutcome.noMatch("@ConditionalOnSingleCandidate " + spec
          + " found no primary candidate amongst the" + " following "
          + matching);    }    matchMessage.append("@ConditionalOnSingleCandidate " + spec + " found "
        + "a primary candidate amongst the following " + matching);
  }  if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { // 相同的逻辑,针对@ConditionalOnMissingBean注解
    BeanSearchSpec spec = new BeanSearchSpec(context, metadata,        ConditionalOnMissingBean.class);    List<String> matching = getMatchingBeans(context, spec);    if (!matching.isEmpty()) {
      return ConditionOutcome.noMatch("@ConditionalOnMissingBean " + spec
          + " found the following " + matching);
    }    matchMessage.append(matchMessage.length() == 0 ? "" : " ");
    matchMessage.append("@ConditionalOnMissingBean " + spec + " found no beans");
  }  return ConditionOutcome.match(matchMessage.toString()); //返回匹配成功的ConditonalOutcome
}

SpringBoot also provides other conditional annotations such as ConditionalOnJava, ConditionalOnNotWebApplication, ConditionalOnWebApplication, ConditionalOnResource, ConditionalOnProperty, ConditionalOnExpression, and interested readers can view their underlying processing logic.

Summary of various condition annotations

Interview must ask: How is the underlying conditional annotation in SpringBoot implemented?

 

Interview must ask: How is the underlying conditional annotation in SpringBoot implemented?

 

Interview must ask: How is the underlying conditional annotation in SpringBoot implemented?

 

Interview must ask: How is the underlying conditional annotation in SpringBoot implemented?

 

Activation mechanism of SpringBoot conditional annotation

After analyzing the execution logic of conditional annotations, the next question is how does SpringBoot make these conditional annotations effective?

SpringBoot uses ConditionEvaluator, an internal class, to complete the analysis and judgment of conditional annotations.

In the refresh process of the Spring container, only classes related to parsing or registering beans will use ConditionEvaluator to complete conditional annotation judgment. In this process, some classes will be skipped if they do not meet the conditions. These classes are for example AnnotatedBeanDefinitionReader, ConfigurationClassBeanDefinitionReader, ConfigurationClassParse, ClassPathScanningCandidateComponentProvider, etc.

For example, the constructor of ConfigurationClassParser will initialize the internal property conditionEvaluator:

public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
    ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,    BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {  this.metadataReaderFactory = metadataReaderFactory;
  this.problemReporter = problemReporter;
  this.environment = environment;
  this.resourceLoader = resourceLoader;
  this.registry = registry;
  this.componentScanParser = new ComponentScanAnnotationParser(
      resourceLoader, environment, componentScanBeanNameGenerator, registry);  // 构造ConditionEvaluator用于处理条件注解
  this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
}
    
ConfigurationClassParser对每个配置类进行解析的时候都会使用ConditionEvaluator:
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
  return;
}

The skip method of ConditionEvaluator:

public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
  // 如果这个类没有被@Conditional注解所修饰,不会skip
  if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
    return false;
  }
  // 如果参数中沒有设置条件注解的生效阶段
  if (phase == null) {
    // 是配置类的话直接使用PARSE_CONFIGURATION阶段
    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<Condition>();
  // 获取配置类的条件注解得到条件数据,并添加到集合中
  for (String[] conditionClasses : getConditionClasses(metadata)) {
    for (String conditionClass : conditionClasses) {
      Condition condition = getCondition(conditionClass, this.context.getClassLoader());
      conditions.add(condition);
    }
  }
  // 对条件集合做个排序
  AnnotationAwareOrderComparator.sort(conditions);
  // 遍历条件集合
  for (Condition condition : conditions) {
    ConfigurationPhase requiredPhase = null;
    if (condition instanceof ConfigurationCondition) {
      requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
    }
    // 没有这个解析类不需要阶段的判断或者解析类和参数中的阶段一致才会继续进行
    if (requiredPhase == null || requiredPhase == phase) {
      // 阶段一致切不满足条件的话,返回true并跳过这个bean的解析
      if (!condition.matches(this.context, metadata)) {
        return true;
      }
    }
  }
  return false;
}

The analysis log of SpringBoot's condition annotations is recorded in the ConditionEvaluationReport class, which can be obtained through BeanFactory (BeanFactory has a parent-child relationship; each BeanFactory has a ConditionEvaluationReport, which is not related to each other):

ConditionEvaluationReport conditionEvaluationReport = beanFactory.getBean("autoConfigurationReport", ConditionEvaluationReport.class);
Map<String, ConditionEvaluationReport.ConditionAndOutcomes> result = conditionEvaluationReport.getConditionAndOutcomesBySource();
for(String key : result.keySet()) {
    ConditionEvaluationReport.ConditionAndOutcomes conditionAndOutcomes = result.get(key);
    Iterator<ConditionEvaluationReport.ConditionAndOutcome> iterator = conditionAndOutcomes.iterator();    while(iterator.hasNext()) {
        ConditionEvaluationReport.ConditionAndOutcome conditionAndOutcome = iterator.next();        System.out.println(key + " -- " + conditionAndOutcome.getCondition().getClass().getSimpleName() + " -- " + conditionAndOutcome.getOutcome());
    }}

Print out the class loading information under conditional annotations:

.......
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classes not found: freemarker.template.Configuration,org.springframework.ui.freemarker.FreeMarkerConfigurationFactory
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classes not found: groovy.text.markup.MarkupTemplateEngine
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classes not found: com.google.gson.Gson
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classes not found: org.h2.server.web.WebServlet
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classes not found: org.springframework.hateoas.Resource,org.springframework.plugin.core.Plugin
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration -- OnClassCondition -- required @ConditionalOnClass classes not found: com.hazelcast.core.HazelcastInstance
.......

 

Make a little progress every day, Peace!

Like it can help the author to pay attention, thank you for your support!

Guess you like

Origin blog.csdn.net/GYHYCX/article/details/108869035