@Conditional series annotations in Spring framework


1 @Contidional Introduction

Insert image description here

Conditional is an annotation provided by SpringFramework, located in the org.springframework.context.annotation package and defined as follows.

@Retention(RetentionPolicy.RUNTIME)
@Target({
    
    ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
    
    
 
    Class<? extends Condition>[] value();
 
}

SpringBoot modules make extensive use of @Conditional annotations. We can use Spring's @Conditional annotations in the following scenarios:

  • Can be directly or indirectly associated with @Component as a class-level annotation, including the @Configuration class;
  • Can be used as a meta-annotation for automatically writing constructive annotations;
  • As a method-level annotation, it works on any @Bean method.

1.1 Condition interface

We need a class to implement the Condition interface provided by Spring, which will match the methods that @Conditional conforms to, and then we can use the class we defined in the @Conditional annotation to check.

public interface Condition {
    
    
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

1.2 Spring @Conditional annotation example

Act on method

Let's take a look at a simpler example first. Let's assume that there are three roles: Teacher, Student and Parent. There are three environments: Linux, Windows and MacOSX. If it is a Linux environment, register the Teacher. If it is a Windows environment, register the Parent. If If it is a Mac environment, register as Student. The code example is as follows:

  • First create Teacher and Student objects without any properties or methods, just an empty class
//如果当前工程运行在Windows系统下,就注册Student
public class Student {
    
    }
 
//如果当前工程运行在Linux系统下,就注册Teacher
public class Teacher {
    
    }
 
// 如果是Mac OSX 系统,就注册Parent
public class Parent {
    
    }
  • Create a LinuxCondition and a WindowsCondition. The LinuxCondition can match the Linux environment, the WindowsCondition can match the Windows environment, and the MacOSX system can match the mac environment.
 
public class LinuxCondition implements Condition {
    
    
 
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
            // 获取系统环境的属性
          String systemName = context.getEnvironment().getProperty("os.name");
          if(systemName.contains("Linux")){
    
    
              return true;
          }
          return false;
    }
}
 
//自定义一个判断条件
public class WindowsCondition implements Condition {
    
    
 
    /*
     * ConditionContext context: spring容器上下文环境
     * AnnotatedTypeMetadata metadata :@Conditional修饰类型信息
     */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
            
           String systemName = context.getEnvironment().getProperty("os.name");
           if(systemName.contains("Windows")){
    
    
               return true;
           }
        return false;
    }
 
}
 
public class OsxCondition implements Condition {
    
    
 
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
        String property = context.getEnvironment().getProperty("os.name");
        if(property.equals("Mac OS X")){
    
    
            return true;
        }
        return false;
    }
}
  • Next, create a new matching registration environment. If the system is a Linux environment, register Teacher, if the system is Windows, register Parent, and if it is Mac system, register Student
 
@Configuration
public class AppConfig {
    
    
 
    @Conditional(OsxCondition.class)
    @Bean
    public Student student(){
    
    
        return new Student();
    }
 
    @Conditional(LinuxCondition.class)
    @Bean
    public Teacher teacher(){
    
    
        return new Teacher();
    }
 
    @Conditional(WindowsCondition.class)
    @Bean
    public Parent parent(){
    
    
        return new Parent();
    }
}
  • Create a new test class for testing
 
public class ConditionTest {
    
    
 
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        String[] names = context.getBeanDefinitionNames();
        for(String name : names){
    
    
            System.out.println("name = " + name);
        }
    }
}

It can be seen from the output that name = student is output to the console. In other words, the system environment I am currently using is the MacOSX environment, so the OSXCondition is registered, which is the student bean.

Manually set up the system environment

You can also manually modify vm.options to change the current system environment to Linux or Windows. Take Idea as an example:

img

Find the vm.options option in Edit Configurationsand change the system environment to Linux, as follows:

img

Then restart the test and find that the Teacher has been injected. Change the current environment to Windows and observe that the Parent is also injected and output.

Acts on class

The @Conditional annotation can be applied to a class, indicating that all beans under this class can be injected after meeting the conditions. It is usually used together with the @Configuration annotation.

  • Create a new one AppClassConfig, mark the @Conditional() annotation on the class, and configure the relevant beans, as follows:
@Conditional(value = OsxCondition.class)

The above indicates that if it is OsxCondition.class, register student, teacher, and parent.

  • There is no need to modify the test class. Just use the original test class for testing. It is found that student, teacher, and parent are all registered.

Multiple condition classes

Because the value method of the @Conditional annotation passes an array by default, it can accept multiple conditions. In order to test the following situation,

Create a new TestConditionclass as follows:

// 单纯为了测试
public class TestCondition implements Condition {
    
    
 
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    
    
        // 返回false,表示不匹配
        return false;
    }
}

edit a bitAppClassConfig

@Conditional(value = {
    
    OsxCondition.class,TestCondition.class})

That is to say, an additional parameter TestCondition.class is added to @Conditional.

After starting the previous test class, it was found that none of the above beans were injected. That is to say, the corresponding bean will be injected only when both OsxCondition.class and TestCondition.class are true . Modify the matches method of TestCondition.class. The return value is true, re-observe the return result and find that all the above beans have been injected.

1.3 Comparison between @Conditional and @Profile

@Spring3.0 also has some annotations similar to @Conditional, which are Spring SPEL expressions and Spring Profiles annotations. The @Conditional annotation of Spring4.0 is more advanced than the @Profile annotation. The @Profile annotation is used to load the application's environment. The @Profile annotation is limited to writing conditional checks based on predefined properties. The @Conditional annotation does not have this restriction.

The @Profile and @Conditional annotations in Spring are used to check the semantics of "If...then...else". However, Spring 4 @Conditional is a more general approach to the @Profile annotation.

  • @Profiles in Spring 3 is only used for writing conditional checks based on Environment variables. Configuration files can be used to load application configurations based on the environment.
  • Spring 4 @Conditional annotation allows developers to define user-defined strategies for conditional checks. @Conditional can be used for conditional bean registration.

2 Spring boot extension

SpringBoot's spring-boot-autoconfigure module also provides Conditional series of related annotations. These annotations can help developers load the required beans according to certain conditions.

Insert image description here

2.1 @ConditionalOnClass and @ConditionalOnMissingClass annotations

​ When the bean loaded by Spring is marked by the @ConditionOnClass annotation, the class loader will first find the specified Class. If the target Class is not found, the class marked by the ConditionOnClass annotation will not be loaded by Spring. On the contrary, ConditionalOnMissingBean means that if there is no Find the target Class, then load the class.

2.2 @ConditionalOnBean and @ConditionalOnMissingBean annotations

​ When the bean loaded by Spring is marked by the @ConditionalOnBean annotation, the specified bean will be found first. If the target bean is not found, the class marked by @ConditionalOnBean will not be loaded by Spring. On the contrary, ConditionalOnMissingBean means that if there is no Class, then Just load the Bean.

​ Let's look at an example. When Dubbo does automatic assembly with Springboot, it first looks for the Bean BASE_PACKAGES_BEAN_NAME. If the Bean does not exist, the Bean serviceAnnotationBeanProcessor will not be loaded by Spring.

@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true)
@Configuration
@AutoConfigureAfter(DubboRelaxedBindingAutoConfiguration.class)
@EnableConfigurationProperties(DubboConfigurationProperties.class)
@EnableDubboConfig
public class DubboAutoConfiguration {
    
    
 
    /**
     * Creates {@link ServiceAnnotationPostProcessor} Bean
     * dubbo.scan.base-packages
     * @param packagesToScan the packages to scan
     * @return {@link ServiceAnnotationPostProcessor}
     */
    @ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME)
    // 先找BASE_PACKAGES_BEAN_NAME 这个bean, 如果没有这个bean, 那么serviceAnnotationBeanProcessor不会被Spring装载。
    @ConditionalOnBean(name = BASE_PACKAGES_BEAN_NAME)
    @Bean
    public ServiceAnnotationPostProcessor serviceAnnotationBeanProcessor(@Qualifier(BASE_PACKAGES_BEAN_NAME)
                                                                       Set<String> packagesToScan) {
    
    
        return new ServiceAnnotationPostProcessor(packagesToScan);
    }
 
}

​ Use the @ConditionalOnMissingBean annotation to define the BASE_PACKAGES_BEAN_NAME Bean

/**
 * Dubbo Relaxed Binding Auto-{@link Configuration} for Spring Boot 2.0
 *
 * @see DubboRelaxedBindingAutoConfiguration
 * @since 2.7.0
 */
@Configuration
@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true)
@ConditionalOnClass(name = "org.springframework.boot.context.properties.bind.Binder")
@AutoConfigureBefore(DubboRelaxedBindingAutoConfiguration.class)
public class DubboRelaxedBinding2AutoConfiguration {
    
    
 
    public PropertyResolver dubboScanBasePackagesPropertyResolver(ConfigurableEnvironment environment) {
    
    
        ConfigurableEnvironment propertyResolver = new AbstractEnvironment() {
    
    
            @Override
            protected void customizePropertySources(MutablePropertySources propertySources) {
    
    
                Map<String, Object> dubboScanProperties = getSubProperties(environment.getPropertySources(), DUBBO_SCAN_PREFIX);
                propertySources.addLast(new MapPropertySource("dubboScanProperties", dubboScanProperties));
            }
        };
        ConfigurationPropertySources.attach(propertyResolver);
        return propertyResolver;
    }
 
    /**
     * The bean is used to scan the packages of Dubbo Service classes
     * 如果没有就创建
     * @param environment {@link Environment} instance
     * @return non-null {@link Set}
     * @since 2.7.8
     */
    @ConditionalOnMissingBean(name = BASE_PACKAGES_BEAN_NAME)
    @Bean(name = BASE_PACKAGES_BEAN_NAME)
    public Set<String> dubboBasePackages(ConfigurableEnvironment environment) {
    
    
        PropertyResolver propertyResolver = dubboScanBasePackagesPropertyResolver(environment);
        return propertyResolver.getProperty(BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet());
    }
 
    @ConditionalOnMissingBean(name = RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME, value = ConfigurationBeanBinder.class)
    @Bean(RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME)
    @Scope(scopeName = SCOPE_PROTOTYPE)
    public ConfigurationBeanBinder relaxedDubboConfigBinder() {
    
    
        return new BinderDubboConfigBinder();
    }
 
}

2.3 @ConditionalOnProperty annotation

​ The function of this annotation is to parse the configuration generation conditions in application.yml/application.properties to take effect. It is also used together with the @Configuration annotation.

Attributes Function other
prefix Read the attribute whose prefix value is prefix in the configuration, and return false if not
name Read the Key value in the attribute configuration. If prefix is ​​configured, you need to splice the prefix first and then match the havingValue value.
havingValue Match the value in the attribute
matchIfMissing Whether to match when the corresponding configuration is not found. The default is false. If it is true and no configuration is found, it will also match.

​ Usage scenarios, for example, when specifying a data source, specify the type of datasource. For example, include the following configuration to use the Hikari data source.

spring.datasource.type=com.zaxxer.hikari.HikariDataSource

img

​ When using, generally set matchIfMissing=false, so that if the conditions are not matched, Spring will automatically skip the configuration class when scanning beans.

​ You can also set matchIfMissing=true. In this scenario, such as caching, we can configure caching to be enabled by default.

@ConditionalOnProperty(name={
    
    cache.effect},marchIfMissing=true)
 
public class CacheAutoConfiguration{
    
    
 
 
 
   // ...
 
}

​ If cache.effect=false is configured in application.properties, then the configuration class will be skipped, so that the configuration will disable the cache.

Guess you like

Origin blog.csdn.net/ZGL_cyy/article/details/132866627