>>>> 😜😜😜 Github: 👉 github.com/black-ant CASE Backup: 👉 gitee.com/antblack/ca…

A. The preface

In this chapter, I will look at the usage and principle of Conditional, and first look at the overall architecture

2. Use

  • ConditionalOnBean: when the container contains the specified Bean.
  • ConditionalOnMissingBean: when there is no specified Bean in the container.
  • @ ConditionalOnSingleCandidate: when specifying the Bean in the container is only one, or even though there are multiple specify preferred Bean
  • ConditionalOnClass: when the classpath has a specified class.
  • @ ConditionalOnMissingClass: when there is no classpath under the condition of the specified class.
  • ConditionalOnProperty: Does the specified attribute have a specified value
  • ConditionalOnResource: Does the classpath have a specified value
  • ConditionalOnExpression: Based on the SpEL expression as the judgment condition.
  • ConditionalOnJava: ConditionalOnJava version
  • ConditionalOnJndi: difference at the specified location in the presence of JNDI
  • @ ConditionalOnNotWebApplication: under the condition of the current project is not the Web project
  • @ ConditionalOnWebApplication: under the condition of the current project is Web purpose

2.1 Basic Usage

2.2 Custom Conditionality

public class DefaultConditional implements Condition {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        logger.info("------> Make Conditional judgment <-------");

        return false; }}Copy the code

2.3 Basic Use

@Bean
@ConditionalOnBean(TestService.class)
public ConfigBean getConfigBean(a) {
    logger.info("------> ConditionalOnBean <-------");
    return new ConfigBean();
}
Copy the code

3. Analysis of the principle of customization

3.1 Conditional entry

For the Configuration. @ the Bean’s creation method, is the starting point of Conditinal refush# invokeBeanFactoryPostProcessors, In which invokes the ConfigurationClassPostProcessor for processing

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {

  // Step 1: Obtain the metadata information of @bean in the current Configuration
  ConfigurationClass configClass = beanMethod.getConfigurationClass();
  MethodMetadata metadata = beanMethod.getMetadata();
  String methodName = metadata.getMethodName();

  // Determine whether the current Bean should be skipped
  if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
     configClass.skippedBeanMethods.add(methodName);
     return;
  }
  if (configClass.skippedBeanMethods.contains(methodName)) {
     return; }}Copy the code

3.2 The process of Conditional judgment

ConditionEvaluator is the core handler class that eventually calls shouldSkip to decide whether to skip

// C- ConditionEvaluator
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {

    // Step 1: Return if the current Bean does not contain @Conditional
   if (metadata == null| |! metadata.isAnnotated(Conditional.class.getName())) {return false;
   }

    // Step 2: There are two types of ConfigurationPhase, representing two configuration phases
    // PARSE_CONFIGURATION: The Condition should be evaluated when the @configuration class is parsed. If the Condition does not match, the @Configuration class will not be added
    // REGISTER_BEAN: The condition does not prevent @Configuration classes from being added. All @Configuration classes will be resolved when the condition is evaluated
   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: Get all Condition objects
   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: Sort
   AnnotationAwareOrderComparator.sort(conditions);

   for (Condition condition : conditions) {
      ConfigurationPhase requiredPhase = null;
      if (condition instanceof ConfigurationCondition) {
         requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
      }
      // Step 5: Final Condition matching process
      if ((requiredPhase == null|| requiredPhase == phase) && ! condition.matches(this.context, metadata)) {
         return true; }}return false;
}
Copy the code

All the way up to here you start matching methods

4. Conventional loading mode

parsing

Known Conditional are implemented based on SpringBootCondition, its specific abstract class is FilteringSpringBootCondition, look at the main inheritance relationships

Remove classes that do not need to be configured

The first step is to quickly remove unnecessary classes. The main process is as follows:

  • Starting point: AbstractApplicationContext # # invokeBeanFactoryPostProcessors refresh
  • Processing: ConfigurationClassPostProcessor # postProcessBeanDefinitionRegistry main logic processing
  • ConfigurationClassFilter # filter
  • Matching: FilteringSpringBootCondition # match

Note that a match is being made to get an AutoConfigurationEntry based on AnnotationMetadata of the Configuration class being imported

4.1 the Filter interceptor

Filter interception is in ConfigurationClassFilter, where all Conditional interceptions are processed

private static class ConfigurationClassFilter {

   // Automatically configured metadata
   private final AutoConfigurationMetadata autoConfigurationMetadata;

   List<String> filter(List<String> configurations) {
      long startTime = System.nanoTime();
      
      // Here are all Confiturations classes
      String[] candidates = StringUtils.toStringArray(configurations);
      boolean skipped = false;
      
      / / OnBeanCondition included here, OnClassCondition, OnWebApplicationCondition 3 kinds
      for (AutoConfigurationImportFilter filter : this.filters) {
         // Gets whether a match match exists
         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;
      }
      
      // If it cannot be skipped, record the current Confiturations class
      List<String> result = new ArrayList<>(candidates.length);
      for (String candidate : candidates) {
         if(candidate ! =null) { result.add(candidate); }}returnresult; }}Copy the code

4.2 FilteringSpringBootCondition match in the match

Here we override the match method of its parent class

public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
   // Step 1: Prepare a Report object for recording
   ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
   // Step 2: Get all the corresponding Condition methods
   ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
   
   boolean[] match = new boolean[outcomes.length];
   for (int i = 0; i < outcomes.length; i++) {
   
      // Assign the array in match to true when outcomes matches ConditionOutcome. Otherwise, return false
      match[i] = (outcomes[i] == null || outcomes[i].isMatch());
      if(! match[i] && outcomes[i] ! =null) {
          // Log
         logOutcome(autoConfigurationClasses[i], outcomes[i]);
         if(report ! =null) {
            / / # like ConditionEvaluationReport SortedMap deposit evaluation criteria
            report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]); }}}return match;
}
Copy the code

The role of ConditionEvaluationReport

This object records detailed information and log information about condition matching during automatic configuration

public final class ConditionEvaluationReport {

    / / the name of the bean
    private static final String BEAN_NAME = "autoConfigurationReport";
    
    // Create a parent conditional match object
    private static final AncestorsMatchedCondition ANCESTOR_CONDITION = new AncestorsMatchedCondition();
    
    // Store the class name or method name (key), condition evaluation output object (value)
    private final SortedMap<String, ConditionAndOutcomes> outcomes = new TreeMap<>();
    
    // Is the original conditional match object
    private boolean addedAncestorOutcomes;
    // The parent's condition evaluation report object
    private ConditionEvaluationReport parent;
    
    // Record the names of classes that have been excluded from the condition evaluation
    private final List<String> exclusions = new ArrayList<>();
    // Record the name of the candidate class for the conditional evaluation
    private final Set<String> unconditionalClasses = new HashSet<>();

}
Copy the code

4.3 getOutcomes gets

Here, OnBean is taken as an example, where there is a certain correlation:

protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
      AutoConfigurationMetadata autoConfigurationMetadata) {
      
   // Step 1: Initialize an array the size of the processing class
   ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
   
   // Step 2: Iterate through all the autoConfigurationClasses
   for (int i = 0; i < outcomes.length; i++) {
      String autoConfigurationClass = autoConfigurationClasses[i];
      
      if(autoConfigurationClass ! =null) {
          
         Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
         / / determine whether exist ConditionalOnBean labeling method
         outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
         
         // Decide whether ConditionOutcome should be output
         if (outcomes[i] == null) {
            Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
                  "ConditionalOnSingleCandidate");
            // Return whether to processoutcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class); }}}return outcomes;
}
Copy the code

4.4 How to determine whether the evaluation criteria are met

Note the matches and FilteringSpringBootCondition isn’t one

  • FilteringSpringBootCondition # match: based on the AutoConfigurationImportFilter, automatic configuration of a given class candidate application filter
  • SpringBootCondition # matches: The final judgment needs to be returned

Calling process

  • In loadBeanDefinitionsForBeanMethod and similar process invocation shouldSkip to jump to the logic
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

   // Get the method name or class name of the annotation
   String classOrMethodName = getClassOrMethodName(metadata);
   try {
   
      Metadata instanceof (ClassMetadata instanceof)
      ConditionOutcome outcome = getMatchOutcome(context, metadata);
      
      // Very simple print log, Trace level
      logOutcome(classOrMethodName, outcome);
      
      / / record results, through ConditionEvaluationReport and recordEvaluation method implementation
      recordEvaluation(context, classOrMethodName, outcome);
      
      // Returns whether the match was successful
      return outcome.isMatch();
   }
   catch (NoClassDefFoundError ex) {
      throw newIllegalStateException(......) ; }catch (RuntimeException ex) {
      throw new IllegalStateException("Error processing condition on "+ getName(metadata), ex); }}Copy the code

The whole point here is to call getMatchOutcome and say yes or no, and getMatchOutcome needs to be subclassed

5. GetMatchOutcome Details

5.1 OnClass

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
   // Step 1: Get the current container ClassLoader
   ClassLoader classLoader = context.getClassLoader();
   ConditionMessage matchMessage = ConditionMessage.empty();
   / / Step 2: determine whether have ConditionalOnClass constraints
   List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
   if(onClasses ! =null) {
      // Step 2-1: filter to check whether classes are missing
      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: Build matchMessage
      matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
            .found("required class"."required classes")
            .items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
   }
   
   // Step 3: Similarly, determine whether MissClasses are needed
   List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
   if(onMissingClasses ! =null) {
      / /... ConditionalOnClass
   }
   return ConditionOutcome.match(matchMessage);
}
Copy the code

ConditionMessage

5.2 OnBean

Similar to onBeans, one is shown here

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
   ConditionMessage matchMessage = ConditionMessage.empty();
   
   
   MergedAnnotations annotations = metadata.getAnnotations();
   
   if (annotations.isPresent(ConditionalOnBean.class)) {
      // Get ConditionalOnBean annotations
      Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
      // Step 2: Return the matching result
      / / its internal by getNamesOfBeansIgnoredByType getBeanNamesForType methods such as to determine whether a 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());
   }
   if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
        / /...
   }
   if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
        / /...
   }
   return ConditionOutcome.match(matchMessage);
}
Copy the code

conclusion

Conditionality itself is not difficult, this part is mainly to prepare for perfecting the atlas and the subsequent starter start process scheme.

There are a few links in the whole process to understand:

  • Conditional in Spring inherits SpringBootCondition and implements its getOutcomes method
  • GetOutcomes is used to quickly remove Configuration that does not need to be loaded, and getMatchOutcome is used to verify matching relationships
  • ShouldSkip in ConditionEvaluator is usually used to determine whether the @bean process needs to be skipped