Spring conditional assembly annotations: @Conditional and its derivative extension annotations

Conditional assembly is a major feature of Spring Boot. It decides whether to assemble Bean according to whether the specified conditions are met, and achieves dynamic flexibility. The automatic configuration class of the starter uses @ Conditional and its derivative extension annotations @ConditionalOnXXX achieve automatic assembly, so following the Spring Boot automatic configuration principle summarized before and custom packaging a starter, today analyze the conditional assembly annotations of the automatic configuration class in the starter.

Project Recommendation: Based on SpringBoot2.x, SpringCloud and SpringCloudAlibaba enterprise-level system architecture underlying framework encapsulation, it solves common non-functional requirements during business development, prevents repeated wheel creation, and facilitates rapid business development and enterprise technology stack Framework unified management. Introduce the idea of componentization to achieve high cohesion and low coupling and highly configurable, so as to be pluggable. Strictly control package dependencies and unified version management to minimize dependencies. Pay attention to code specifications and comments, very suitable for personal learning and enterprise use

Github address: https://github.com/plasticene/plasticene-boot-starter-parent

Gitee address: https://gitee.com/plasticene3/plasticene-boot-starter-parent

WeChat Public Account: Shepherd Advanced Notes

1 @Conditional

@Conditional: This annotation is newly added in spring4. Its function, as the name implies, is to judge according to certain conditions. Only when the conditions are met, the bean is injected into the container. The source code of the annotation 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();

}

It can be seen from the code that this annotation can be applied to classes and methods, and at the same time there is only one attribute value, which is a Class array, and needs to inherit or implement the Condition interface:

@FunctionalInterface
public interface Condition {<!-- -->
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

@FunctionalInterface: Indicates that the interface is a functional interface, that is, functional programming and lambda expressions can be used.

Condition is an interface that needs to implement the matches method. If returns true, it will be injected into the bean, and if it is false, it will not be injected.

Summary: The @Conditional annotation rewrites the matches method of the Condition interface by passing in one or more implementation classes of the Condition interface. The conditional logic is in this method and acts on the place where the bean is created.

According to the above description, next I simulate the scene of switching different languages in multi-language environment condition assembly:

Language

@Data
@Builder
public class Language {<!-- -->
    private Long id;
    private String content;
}

Condition:

public class ChineseCondition implements Condition {<!-- -->
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {<!-- -->
        Environment environment = context. getEnvironment();
        String property = environment. getProperty("lang");
        if (Objects.equals(property, "zh_CN")) {<!-- -->
            return true;
        }
        return false;
    }
}
public class EnglishCondition implements Condition {<!-- -->
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {<!-- -->
        Environment environment = context. getEnvironment();
        String property = environment. getProperty("lang");
        if (Objects.equals(property, "en_US")) {<!-- -->
            return true;
        }
        return false;
    }
}

Configuration class:

@Configuration
public class MyConfig {<!-- -->
    
    @Bean
    @Conditional(ChineseCondition. class)
    public Language chinese() {<!-- -->
        
        return Language.builder().id(1l).content("Hua Liu is the best").build();
    }

    @Bean
    @Conditional(EnglishCondition. class)
    public Language english() {<!-- -->
      
        return Language.builder().id(2l).content("english is good").build();
    }



    public static void main(String[] args) {<!-- -->
        System.setProperty("lang", "zh_CN");
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        String[] beanDefinitionNames = applicationContext. getBeanDefinitionNames();
        // Traverse the beanName in the Spring container
        for (String beanDefinitionName : beanDefinitionNames) {<!-- -->
            System.out.println(beanDefinitionName);
        }
    }
}

Execution result: chinese, indicating that ChineseCondition returns true according to the condition matching, and the bean is successfully injected.

2 @Condition derived annotation

2.1@ConditionalOnBean

@ConditionalOnBean : When the given bean exists, instantiate the current bean, examples are as follows

 @Bean
    @ConditionalOnBean(name = "address")
    public User (Address address) {<!-- -->
        //Here, if the address entity is not successfully injected, a null pointer will be reported here
        address.setCity("hangzhou");
        address. setId(1l)
        return new User("Phantom", city);
    }

The ConditionalOnBean annotation is added here, indicating that the user will be instantiated only if the address bean exists.

The implementation principle is as follows:

2.2. @ConditionalOnMissingBean

@ConditionalOnMissingBean: When the given bean does not exist, instantiate the current Bean, contrary to @ConditionalOnBean

@Configuration
public class BeanConfig {<!-- -->
 
    @Bean(name = "notebookPC")
    public Computer computer1(){<!-- -->
        return new Computer("Laptop");
    }
 
    @ConditionalOnMissingBean(Computer. class)
    @Bean("reservePC")
    public Computer computer2(){<!-- -->
        return new Computer("Standby Computer");
    }

If ConditionalOnMissingBean has no parameters, it can be seen from the source code that when this annotation has no parameters, it only makes sense when it annotates the method and there is @Bean on the method, otherwise it is meaningless. The meaning is that the name of the return value type of the annotated method is used as the value of the type attribute of ConditionalOnMissingBean.

2.3. @ConditionalOnClass

@ConditionalOnClass: When the given class name exists on the class path, instantiate the current Bean

2.4. @ConditionalOnMissingClass

@ConditionalOnMissingClass: When the given class name does not exist on the class path, instantiate the current Bean

2.5. @ConditionalOnProperty

@ConditionalOnProperty: Spring Boot controls whether Configuration takes effect through @ConditionalOnProperty

@Retention(RetentionPolicy.RUNTIME)
@Target({<!-- --> ElementType. TYPE, ElementType. METHOD })
@Documented
@Conditional(OnPropertyCondition. class)
public @interface ConditionalOnProperty {<!-- -->

    // array, get the value corresponding to the property name, and name cannot be used at the same time
    String[] value() default {<!-- -->};

    // Configure the prefix of the property name, such as spring.http.encoding
    String prefix() default "";

    // Array, full or partial names of configuration properties
    // Can be used in combination with prefix to form a complete configuration attribute name, and cannot be used with value at the same time
    String[] name() default {<!-- -->};

    // It can be used in combination with name to compare whether the obtained attribute value is the same as the value given by havingValue, and the configuration is loaded only if they are the same
    String havingValue() default "";

    // Whether it can be loaded when this configuration property is missing. If it is true, it will be loaded normally without this configuration property; otherwise, it will not take effect
    boolean matchIfMissing() default false; // Whether loose matching is possible, I don't know how to use it so far boolean relaxedNames() default true;}

It is realized by its two attributes name and havingValue, where name is used to read from application.properties Get an attribute value.
If the value is empty, return false;
If the value is not empty, compare the value with the value specified by havingValue, and return true if they are the same; otherwise, return false.
If the return value is false, the configuration will not take effect; if it is true, it will take effect.

Of course, havingValue can also not be set, as long as the value of the configuration item is not false or “false”, the bean will be loaded

Sample code:

feign:
  hystrix:
    enabled: true

Fegin opens the circuit breaker hystrix:

 @Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {<!-- -->
return HystrixFeign.builder();
}

Conclusion: @Conditional and its derivative annotations are to facilitate the program to dynamically inject beans according to the current environment or container conditions, which is the core of Spring Boot conditional assembly implementation.