preface

Spring Boot column +1

I don’t know if you’ve ever used @conditionalxxx annotations in your daily Spring Boot development, such as @conditionalonmissingBean. I believe that Spring Boot source friends must be familiar.

Conditionalxxx comments such as @conditionalxxx indicate that the action will only be executed if a certain condition is true. Master this kind of annotation, help daily development, framework building.

In today’s article, we will introduce this kind of annotation from past life.

The Spring version of the Boot

The version of Spring Boot on which this article is based is 2.3.4.release.

@Conditional

The @Conditional annotations are native to Spring4.0 and can be used for any type or method. The @Conditional annotations allow Conditional judgments to be configured. When all conditions are met, the object marked by @Conditional will be processed by the Spring container.

Conditionality is widely used, for example, to control whether a Bean needs to be registered or not, and there are many variations in Spring Boot, such as @conditionalonmissingBean and @conditionalonbean, as follows:

The annotation of the source code is very simple, only one attribute value, said judge conditions (one or more), is org. Springframework. Context. The annotation. The Condition type, source code is as follows:

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

	/**
	 * All {@link Condition} classes that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();
}
Copy the code

@ Conditional comments implementation principle is simple, it is through the org. Springframework. Context. The annotation. The Condition of the interface to determine whether should perform operations.

Condition interfaces

The @conditional annotation determines whether a Condition is true or not depending on the Condition implementation specified by the value attribute, including a matches() method, which returns true to indicate that the Condition is true or not. The interface is as follows:

@FunctionalInterface
public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
Copy the code

matchesThe two parameters are as follows:

  1. context: Conditional context,ConditionContextInterface type, which can be used to get context information in the container.
  2. metadata: Used to get the quilt@ConditionalAll annotation information on the annotated object

ConditionContext interface

This interface is very important, can obtain many Spring context information, such as ConfigurableListableBeanFactory, source code is as follows:

public interface ConditionContext {

    /** * returns the bean definition registry, through which you can obtain various configuration information for the bean definition */
    BeanDefinitionRegistry getRegistry(a);

    / * * * return ConfigurableListableBeanFactory type of bean factory, the equivalent of an ioc container object * /
    @Nullable
    ConfigurableListableBeanFactory getBeanFactory(a);

    /** * Returns the current Spring container environment configuration information object */
    Environment getEnvironment(a);

    /** * returns the resource loader */
    ResourceLoader getResourceLoader(a);

    /** * returns the class loader */
    @Nullable
    ClassLoader getClassLoader(a);
}
Copy the code

How do I customize Condition?

For example, suppose you have a requirement to inject beans differently depending on the runtime environment, Windows and Linux.

Judgment of implementation is very simple, to define different environment conditions, implement org. Springframework. Context. The annotation. The Condition can be.

Windows environment judge condition source code as follows:

/** * The matching condition of the operating system. If the operating system is Windows, true */ is returned
public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
        // Get the current environment information
        Environment environment = conditionContext.getEnvironment();
        // Get the current system name
        String property = environment.getProperty("os.name");
        // If Windows is included, the system is Windows and true is returned
        if (property.contains("Windows")) {return true;
        }
        return false; }}Copy the code

Linux environment judgment source code is as follows:

/** * The matching condition of the operating system. If the operating system is Windows, true */ is returned
public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
        Environment environment = conditionContext.getEnvironment();

        String property = environment.getProperty("os.name");
        if (property.contains("Linux")) {return true;
        }
        return false; }}Copy the code

The configuration class incorporates @beans to inject different beans as follows:

@Configuration
public class CustomConfig {

    /** * The Bean injected in the Windows environment is winP *@return* /
    @Bean("winP")
    @Conditional(value = {WindowsCondition.class})
    public Person personWin(a){
        return new Person();
    }

    /** * The Bean injected in Linux is LinuxP *@return* /
    @Bean("LinuxP")
    @Conditional(value = {LinuxCondition.class})
    public Person personLinux(a){
        return new Person();
    }
Copy the code

Here’s a simple test:

@SpringBootTest
class SpringbootInterceptApplicationTests {

    @Autowired(required = false)
    @Qualifier(value = "winP")
    private Person winP;

    @Autowired(required = false)
    @Qualifier(value = "LinuxP")
    private Person linP;

    @Test
    void contextLoads(a) { System.out.println(winP); System.out.println(linP); }}Copy the code

Run the unit test in Windows. The output is as follows:

com.example.springbootintercept.domain.Person@885e7ff
null
Copy the code

Obviously, the judgment works and only WINP is injected in the Windows environment.

When are conditional judgments performed?

The execution of conditional judgment is divided into two stages, as follows:

  1. Configurationphase.parse_configuration: This is where you get a batch of configuration classes and some beans to register.

  2. Bean registration phase (ConfigurationPhase REGISTER_BEAN) : the configuration phase to get the configuration of the class and need to register the Bean into the container.

ConfigurationCondition is used in Spring Boot. This interface allows you to customize the execution phase, such as @conditionalonmissingBean, which is executed in the Bean registration phase. Because you need to determine the Bean from the container.

What’s the difference between these two stages? : In fact, it is very simple. The configuration class resolution phase only collects the configuration classes and some beans that need to be loaded (filtered out by the @Conditional annotation), while the Bean registration phase injects the collected beans and configuration classes into the container. If the matches() interface of the Condition interface is executed during the configuration class resolution phase to determine whether some beans exist in the IOC container, this is obviously not possible because they are not registered with the container.

What are configuration classes and what are they? : class methods annotated by @Component, @ComponentScan, @import, @importResource, @Configuration, and class methods with @bean. How to determine the configuration class in the source code has separate methods: org. Springframework. Context. The annotation. ConfigurationClassUtils# isConfigurationCandidate.

ConfigurationCondition interface

This interface adds a getConfigurationPhase() method to the @condition interface to customize the execution phase. The source code is as follows:

public interface ConfigurationCondition extends Condition {

    /** * The phase of the condition judgment, whether to filter when parsing the configuration class or when creating the bean */
    ConfigurationPhase getConfigurationPhase(a);


    /** * indicates the enumeration of stages: 2 values */
    enum ConfigurationPhase {

        /** * Config class parsing phase, if the condition is false, config class will not be parsed */
        PARSE_CONFIGURATION,

        /** * Bean registration phase, if false, bean will not be registered */
        REGISTER_BEAN
    }
}
Copy the code

This interface can be achieved when you need to specify the implementation phase, such as the need according to whether a Bean in the IOC container to inject the specified Bean, you need to specify the execution phase for Bean registered stage (ConfigurationPhase. REGISTER_BEAN).

The execution order of multiple conditions

In @Conditional, multiple conditions can be specified. By default, they are executed in order as follows:

class Condition1 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true; }}class Condition2 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true; }}class Condition3 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true; }}@Configuration
@Conditional({Condition1.class, Condition2.class, Condition3.class})
public class MainConfig5 {}Copy the code

The examples above will followCondition1,Condition2,Condition3The execution.

The default is order, but what about when we need to specify order? Very simple, there are three ways:

  1. implementationPriorityOrderedInterface to specify a priority
  2. implementationOrderedInterface Indicates the interface that specifies the priority
  3. use@OrderAnnotations to specify priorities

Examples are as follows:

@Order(1) 
class Condition1 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true; }}class Condition2 implements Condition.Ordered { 
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

    @Override
    public int getOrder(a) { 
        return 0; }}class Condition3 implements Condition.PriorityOrdered {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

    @Override
    public int getOrder(a) {
        return 1000; }}@Configuration
@Conditional({Condition1.class, Condition2.class, Condition3.class})
public class MainConfig6 {}Copy the code

3, Condtion2, Condtion1, Condtion3, Condtion2, Condtion1, Condtion3, Condtion2, Condtion1

Some common annotations in Spring Boot

Spring Boot makes extensive use of these annotations. Common ones are as follows:

  1. @ConditionalOnBean: instantiates when there are specified beans in the container.
  2. @ConditionalOnMissingBean: instantiates when no Bean is specified in the container.
  3. @ConditionalOnClass: instantiates when the specified class exists on the classpath.
  4. @ConditionalOnMissingClass: instantiates when no class is specified on the classpath.
  5. @ConditionalOnWebApplication: instantiate when the project is a Web project.
  6. @ConditionalOnNotWebApplication: instantiate when the project is not a Web project.
  7. @ConditionalOnProperty: instantiates when the specified property has the specified value.
  8. @ConditionalOnExpression: Conditional judgment based on SpEL expression.
  9. @ConditionalOnJava: Triggers instantiation when the JVM version is in the specified version range.
  10. @ConditionalOnResource: Triggers instantiation when a specified resource is in the classpath.
  11. @ConditionalOnJndi: Triggers instantiation if JNDI exists.
  12. @ConditionalOnSingleCandidate: Triggers instantiation when there is only one specified Bean in the container, or more than one but the preferred Bean is specified.

For example, under the WebMvcAutoConfiguration class of the WEB module, there is this code:

    @Bean
		@ConditionalOnMissingBean
		public InternalResourceViewResolver defaultViewResolver(a) {
			InternalResourceViewResolver resolver = new InternalResourceViewResolver();
			resolver.setPrefix(this.mvcProperties.getView().getPrefix());
			resolver.setSuffix(this.mvcProperties.getView().getSuffix());
			return resolver;
		}
Copy the code

@ of common Bean and @ ConditionalOnMissingBean annotations used in combination, mean when container no InternalResourceViewResolver in this type of Bean will be injected. What good is this? The obvious benefit is that developers can customize the view resolver they want, or if they don’t have one, use the default, which is Spring Boot’s convenience for customizing configurations.

conclusion

There are many annotations in the evolution of @Conditional annotations in Spring Boot, which need to be emphasized, especially in the later framework integration.