Super detailed analysis of Spring’s @Conditional annotation

Foreword

The known @Conditional annotation is used to specify conditions that can be registered as a bean in the container. Then this article will combine the sample project, start from the source code, and analyze the following aspects of the @Conditional annotation.

  1. The timing of the @Conditional annotation;
  2. The execution order of Condition;
  3. The relationship between multiple Condition.

Springboot version: 2.4.1

Spring version: 5.3.2

Text

1. Example project construction

A sample project structure is shown below.

The Condition interface implementation class is as follows.

public class MyControllerCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

}

public class MyDaoCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

}

public class MyFurtherCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

}

public class MyRepositoryCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

}

public class MyServiceCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

}
Copy Code

The business class definition is as follows.

@Controller
@Conditional(MyControllerCondition. class)
public class MyController {}

@Conditional(MyDaoCondition. class)
public class MyDao {}

public class MyFurtherService {}

@Conditional(MyRepositoryCondition. class)
public class MyRepository {}

public class MyService {}
Copy Code

The configuration class MyFurtherConfig is defined as follows.

@Configuration
@Conditional(MyFurtherCondition. class)
public class MyFurtherConfig {

    @Bean
    public MyFurtherService myFurtherService() {
        return new MyFurtherService();
    }

}
Copy Code

The configuration class MyConfig is defined as follows.

@ComponentScan
@Configuration
@Import(MyDao. class)
public class MyConfig {

    @Bean
    @Conditional(MyServiceCondition. class)
    public MyService myService() {
        return new MyService();
    }

    @Bean
    public MyRepository myRepository() {
        return new MyRepository();
    }

}
Copy Code

The test class is shown below.

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig. class);
    }

}
Copy Code

In the sample project, a total of the following cases of using the @Conditional annotation are demonstrated.

  1. The business class is annotated by @Conditional and @Controller (@Service, @Repository and @Component< /strong>Annotations are acceptable), and the business class is scanned and registered in the container through the @ComponentScan annotation
    1. MyController
  2. The business class is modified by @Conditional annotation, and the business class is directly imported and registered into the container through @Import annotation
    1. MyDao
  3. The business class is registered in the container through the method modified by the @Bean annotation, and the method modified by the @Bean annotation is modified by the @Conditional annotation
    1. MyService
  4. The configuration class is decorated by @Conditional annotation and @Configuration annotation, and the configuration class is scanned and registered in the container by @ComponentScan annotation
    1. MyFurtherConfig

There is also a case where the @Conditional annotation is invalid.

  1. The business class is modified by @Conditional annotation, and the business class is registered in the container by the method modified by @Bean annotation
    1. MyRepository

Two. @Conditional action time

When a class modified by the @Conditional annotation is registered as a BeanDefinition in Spring, an additional step of conditional judgment will be performed. If the judgment returns < strong>true, then continue to execute the logic of registering as BeanDefinition, otherwise give up the registration.

The @Conditional annotation needs to be used in conjunction with the Condition interface. When using the @Conditional annotation, it needs to be imported through the @Conditional annotationCondition interface will call implemented by the implementation class of the Condition interface in some stages of registering BeanDefinition with the container. matches() method to judge. The following is the stage of calling the matches() method implemented by the implementation class of the Condition interface to make a judgment.

  1. When the processConfigurationClass() method of ConfigurationClassParser parses ConfigurationClass, if the class corresponding to ConfigurationClass is specified by @Conditional< /strong> annotation modification, then it will call the Condition interface to implement the judgment logic of the class;
  2. When the doProcessConfigurationClass() method of ConfigurationClassParser parses the @ComponentScan annotation of the class corresponding to ConfigurationClass, it will be called to parse() method of >ComponentScanAnnotationParser enables the processing of @ComponentScan annotation content, within the scope of @ComponentScan annotation scanning When each target class creates a BeanDefinition, if the target class is modified by @Conditional annotation, then the judgment logic of the Condition interface implementation class will be called ( Target class: the class annotated by @Controller, @Service, @Repository or @Configuration);
  3. When ConfigurationClassBeanDefinitionReader parses ConfigurationClass into BeanDefinition, if the class corresponding to ConfigurationClass is specified by @Conditional >Annotation decoration, then it will call the Condition interface to implement the judgment logic of the class, if the judgment return value is false and ConfigurationClass has been registered in the registry If there is a BeanDefinition, then this BeanDefinition needs to be removed from the registry;
  4. When ConfigurationClassBeanDefinitionReader parses ConfigurationClass into BeanDefinition, when processing each BeanMethod, if BeanMethodThe corresponding method is modified by the @Conditional annotation, then the Condition interface will be called to implement the judgment logic of the class.

The timing diagram is as follows.

(If you can’t see clearly, you can click on the picture and enlarge it)

Three. Condition execution order

The implementation class of the Condition interface can be imported by the @Conditional annotation, and the @Conditional annotation can import multiple Condition at one time > the implementation class of the interface, and are executed in import order by default. An example is given below for illustration.

The business is as follows.

public class MyService {}
Copy Code

Define two implementation classes of the Condition interface (hereinafter referred to as condition classes), as shown below.

public class MyFirstCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

}

public class MySecondCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

}
Copy Code

The configuration class is as follows, pay attention to the import order of the conditional classes.

@Configuration
public class MyConfig {

    @Bean
    @Conditional({MySecondCondition. class, MyFirstCondition. class})
    public MyService myService() {
        return new MyService();
    }

}
Copy Code

The test class is shown below.

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig. class);
    }

}
Copy Code

The printed results are as follows.

It can be seen that the calling order of the conditional classes is consistent with the importing order of the conditional classes. If you want to specify the order, you can let the condition class implement the Ordered interface, or implement the PriorityOrdered interface, or use the @Order annotation to modify the condition class. Based on the above example, add three more conditional classes as shown below.

public class MyFirstOrderCondition implements Condition, Ordered {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

    public int getOrder() {
        return 10;
    }

}

public class MySecondOrderCondition implements Condition, PriorityOrdered {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

    public int getOrder() {
        return 10;
    }

}

@Order(5)
public class MyThirdOrderCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

}
Copy Code

Modify the configuration class again.

@Configuration
public class MyConfig {

    @Bean
    @Conditional({MySecondCondition.class, MyFirstCondition.class,
            MyFirstOrderCondition.class, MySecondOrderCondition.class, MyThirdOrderCondition.class})
    public MyService myService() {
        return new MyService();
    }

}
Copy Code

Run the test program and print as follows.

The above printing results can be summarized as follows.

  1. The condition class that implements the PriorityOrdered interface is always executed before the condition class that implements the Ordered interface;
  2. The condition class with a small order value is always executed before the condition class with a large order value;
  3. The PriorityOrdered interface is implemented, the Ordered interface or the condition class modified by the @Order annotation is always executed before the normal condition class.

Four. The relationship between multiple Conditions

If a @Conditional annotation imports multiple conditional classes, what is the relationship between the judgment results of these conditional classes? Let’s analyze this issue below. There is a class called ConditionEvaluator in Spring. The shouldSkip() method of this class is specially used to call the judgment logic of the condition class, as shown below .

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    // If the current class is not modified by the @Conditional annotation, return false directly, indicating that the current class should not be skipped
    if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
        return false;
    }

    if (phase == null) {
        if (metadata instanceof AnnotationMetadata & amp; & amp;
                ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
            return shouldSkip(metadata, ConfigurationPhase. PARSE_CONFIGURATION);
        }
        return shouldSkip(metadata, ConfigurationPhase. REGISTER_BEAN);
    }

    // Instantiate all condition classes and add them to the conditions collection
    List<Condition> conditions = new ArrayList<>();
    for (String[] conditionClasses : getConditionClasses(metadata)) {
        for (String conditionClass : conditionClasses) {
            Condition condition = getCondition(conditionClass, this.context.getClassLoader());
            conditions. add(condition);
        }
    }

    AnnotationAwareOrderComparator.sort(conditions);

    // Traverse each condition class and call its matches() method to judge
    // As long as the matches() method of a conditional class returns false, it means that the current class should be skipped
    for (Condition condition : conditions) {
        ConfigurationPhase requiredPhase = null;
        if (condition instanceof ConfigurationCondition) {
            requiredPhase = ((ConfigurationCondition) condition). getConfigurationPhase();
        }
        if ((requiredPhase == null || requiredPhase == phase) & amp; & amp; !condition.matches(this.context, metadata)) {
            return true;
        }
    }

    return false;
}
Copy Code

When the ConditionEvaluator#shouldSkip method returns true, it means that the current class needs to be skipped, and as long as there is a condition class, the matches() method returns false, the ConditionEvaluator#shouldSkip method will return true, and will not execute the remaining matches() of the condition class method.

That is, multiple Condition are in an AND relationship. Only when all Condition are satisfied can the bean be registered in the container.

Summary

The first is how to use the @Conditional annotation, which is summarized as follows.

  1. The business class is annotated by @Conditional and @Controller (@Service, @Repository and @Component< /strong>Annotations are acceptable), and the business class is scanned and registered in the container through the @ComponentScan annotation;
  2. The business class is modified by @Conditional annotation, and the business class is directly imported and registered into the container through @Import annotation;
  3. The business class is registered in the container through the method modified by @Bean annotation, and the method modified by @Bean annotation is modified by @Conditional annotation;
  4. Configuration classes are decorated with @Conditional annotations and @Configuration annotations, and configuration classes are scanned and registered in containers through @ComponentScan annotations.

Then there is the timing of the @Conditional annotation. The general summary is that in the process of registering an object as a BeanDefinition in the container, Condition will be called in each link The interface implements the judgment logic of the class to judge whether the registration process needs to be terminated.

The following is a summary of the execution order of multiple Condition, as follows.

  1. By default, it is executed in the import order;
  2. The condition class that implements the PriorityOrdered interface is always executed before the condition class that implements the Ordered interface;
  3. The condition class with a small order value is always executed before the condition class with a large order value;
  4. The PriorityOrdered interface is implemented, the Ordered interface or the condition class modified by the @Order annotation is always executed before the normal condition class.

The last is the relationship between multiple Condition, the relationship is the relationship between and , that is, as long as one Condition is not satisfied, then the >bean will not be registered.

syntaxbug.com © 2021 All Rights Reserved.