• Spring Boot 2.x系列【14】功能篇之@Conditional注解


    有道无术,术尚可求,有术无道,止于术。

    本系列Spring Boot版本2.7.0

    前言

    在上篇文档中,我们回顾了Spring 中创建Bean 的各种方式,如果我们需要根据其他条件或者配置项来控制是否注册一个Bean,应该怎么办呢?

    如果是通过@Import导入一个选择器,获取条件再判断是否注册,倒是可以实现,但是十分麻烦。

    Spring Boot已经为我们提供了@Conditional及一系列的派生注解,实现条件控制。

    @Conditional

    conditional翻译过来是【有条件的; 条件的; 附带条件的; 依…而定的】意思,就和if else条件判断语句一样,条件成立时,才进行Bean 的注册。

    @Conditional是Spring 原生框架提供的注解,需要配置一个Condition接口的实现类:

    public @interface Conditional {
        Class<? extends Condition>[] value();
    }
    
    • 1
    • 2
    • 3

    Condition接口中,声明了一个匹配方法,返回真假,为真时进行注册,反之不注册:

    @FunctionalInterface
    public interface Condition {
        boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
    }
    
    • 1
    • 2
    • 3
    • 4

    案例演示:需要根据一个自定义的配置项来决定是否注册一个Bean 。

    首先实现Condition接口,在匹配方法中,查询当前环境中是否配置了某个配置项:

    public class MyCondition implements Condition {
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            // 获取属性 condition
            String condition = context.getEnvironment().getProperty("condition");
            return StringUtils.hasText(condition);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在需要配置该条件类上,添加@Conditional注解:

    @Component
    @Conditional(MyCondition.class)
    public class TestCondition {
    }
    
    • 1
    • 2
    • 3
    • 4

    可以发现,只有在YML 中配置了condition时,才会注册该Bean。

    condition: hahaha
    
    • 1

    也可以自定义一个派生条件注解,使用更加的方便:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional({MyCondition.class})
    public @interface ConditionalOnMyProperty {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    除了写在类上,还可以直接写上方法上:

        @Bean
        @ConditionalOnMyProperty
        TestCondition testCondition(){
            return new TestCondition();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Spring Boot 已经为我们提供了直接使用的条件注解,并在自动配置源码中大量运用:
    在这里插入图片描述
    接下来挑一些常用的注解进行说明和演示。

    @ConditionalOnBean

    在上下文中存在某个Bean 对象时,才会注入当前Bean 。

    源码如下

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional({OnBeanCondition.class})
    public @interface ConditionalOnBean {
    	// 条件Bean Class 数组
        Class<?>[] value() default {};
    	// 条件Bean 的类名,Class.getName()
        String[] type() default {};
    	// 使用某个注解的条件Bean 
        Class<? extends Annotation>[] annotation() default {};
    	// 条件Bean 的Bean 名称
        String[] name() default {};
    	// 条件Bean 容器层级
    	// ALL:所有容器
        // ANCESTORS:所有祖先容器,但不包含当前容器
        // CURRENT:仅当前容器
        SearchStrategy search() default SearchStrategy.ALL;
    	// 条件Bean 的类型为指定类型,并且泛型为当前需要注入的Bean 
        Class<?>[] parameterizedContainer() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    @ConditionalOnMissingBean

    在上下文中不存在某个Bean 对象时,才会注入当前Bean ,是@ConditionalOnBean的反例。

    配置属性同@ConditionalOnBean

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional({OnBeanCondition.class})
    public @interface ConditionalOnMissingBean {
        Class<?>[] value() default {};
    
        String[] type() default {};
    
        Class<?>[] ignored() default {};
    
        String[] ignoredType() default {};
    
        Class<? extends Annotation>[] annotation() default {};
    
        String[] name() default {};
    
        SearchStrategy search() default SearchStrategy.ALL;
    
        Class<?>[] parameterizedContainer() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    @ConditionalOnClass

    某个class存在于类路径上,才会注入当前Bean 。

    源码如下

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional({OnClassCondition.class})
    public @interface ConditionalOnClass {
    	// 指定条件类的Class 类型
        Class<?>[] value() default {};
    	// 指定条件类的类名
        String[] name() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在Spring Boot 自动配置类中,大量使用了该注解,比如引入了MongoDB 客户端包,才会触发MongoDB 的自动配置。
    在这里插入图片描述

    @ConditionalOnMissingClass

    某个class不存在于类路径上,才会注入当前Bean ,是@ConditionalOnClass的反例。

    只能配置一个类名属性:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional({OnClassCondition.class})
    public @interface ConditionalOnMissingClass {
        String[] value() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    @ConditionalOnWebApplication

    当Web 应用程序和指定的类型一致时,才会注入当前Bean ,如果某些Bean 在指定的Web 程序类型时才能使用,这很有效。

    比如当前为SERVLET类型的Web 容器,才会注册TestCondition

    @Component
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    public class TestCondition {
    }
    
    • 1
    • 2
    • 3
    • 4

    @ConditionalOnNotWebApplication则相反。

    @ConditionalOnProperty

    存在某个配置属性值时,才会注入当前Bean,可以在配置文件中添加一个开关属性,实现是否加载当前Bean 的功能。

    源码如下

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Documented
    @Conditional({OnPropertyCondition.class})
    public @interface ConditionalOnProperty {
    	// 对应属性值为真或假,可配置多个,一个为假,都不注册
    	// 和 name() default {}; 不可同时存在
        String[] value() default {};
    	// 属性名前缀
        String prefix() default "";
    	// 属性完整名称或部分名称,可与prefix组合使用,组成完整的配置属性名称
        String[] name() default {};
    	// 是否有属性值
        String havingValue() default "";
    	// 未配置havingValue的值时,是否加载当前Bean 
        boolean matchIfMissing() default false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    比如我们在配置文件中添加一个配置,用于判定是否开启某个功能:

    spring:
      h2:
        console:
          enabled: true
    
    • 1
    • 2
    • 3
    • 4

    可以使用以下两种方式配置:

    @ConditionalOnProperty(value = {"spring.h2.console.enabled"})
    
    @ConditionalOnProperty(
        prefix = "spring.h2.console",
        name = {"enabled"},
        havingValue = "true",
        matchIfMissing = false
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    @Profile

    @Profile可以根据当前激活的环境决定是否注册某个Bean。

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional({ProfileCondition.class})
    public @interface Profile {
        String[] value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这是非常常用的一注解,比如写了一个打印开发日志的切面,切换到测试或者生产环境时,需要关闭,这个时候就可以在切面上添加@Profile,添加一个dev属性,表示只有当spring.profiles.active配置为开发环境时,这个切面才有效。

    @Aspect
    @Profile({"dev"})
    public class RequestLogAspect {
    // 切面
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其他注解

    • @ConditionalOnExpression:当SpEL 表达式为true 。

    • @ConditionalOnJava:当JVM版本为指定的版本范围时。

    • @ConditionalOnResource:当类路径下有指定的资源时。

    • @ConditionalOnJndi:在JNDI存在时。

    • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时。

  • 相关阅读:
    java强应用,软引用,弱引用,虚引用的区别
    Qt-FFmpeg开发-音频解码为PCM文件(9)
    善于利用GPT确实可以解决许多难题
    淘天集团联合爱橙科技开源大模型训练框架Megatron-LLaMA
    系统架构7个非功能性需求
    Blazor入门100天 : 自做一个支持长按事件的按钮组件
    spring ioc
    Springboot中开启多线程,实现异步非阻塞、异步阻塞、有无返回值的场景
    分布式事务(Seata) 四大模式详解
    大师傅敢死队风格
  • 原文地址:https://blog.csdn.net/qq_43437874/article/details/125838825