@Conditional是Spring Boot中大量使用的注解之一,它可以根据是否满足某一个特定条件来决定是否加载指定的Bean。本文带领大家详细了解该注解的基本功能及实战使用。

条件注解@Conditional

@Conditional是SpringFramework提供的注解,位于 org.springframework.context.annotation包内,被其注解的类会根据指定的条件进行判断,如果满足条件则进行Bean的实例化及加载,如果不符合条件则不进行加载。

比如在Spring Boot的自动配置中经常用在这样的场景:当某个待自动配置组件的jar包在类路径下时,自动配置该组件的一个或多个Bean。

除了直接使用@Conditional注解来进行判断,在Spring Boot中通常情况下使用的更多的是由@Conditional组合的具体特殊场景的注解。比如,@ConditionalOnClass注解用来检查类路径下是否有指定的类。其相关使用源码如下:

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

@Conditional使用形式:

  • 类型级别,可以在@Component或是@Configuration类上使用;
  • 原型级别,用于特定自动场景注解上的注解(如上:ConditionalOnClass);
  • 方法级别,作用在任何@Bean方法上(如下例)。

另外,condition注解是不会继承的,如果一个父类使用了conditional注解,其子类是不会拥有conditions的。

源码解析

为了更好的理解@Conditional注解的使用,我们先看一下它的源代码定义:

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

    // 该属性传入实现Conditions的类
    Class<? extends Condition>[] value();
}

通过源码可以看出,Conditional的属性为实现了Condition接口的类的数组。在使用的过程中只有满足了Condition接口实现类定义的条件才会进行实例化操作。

顺便再看一下Condition接口的定义:

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

其中只定义了一个返回类型为boolean的方法,也就是说返回值为true则被注解类进行实例化,返回值为false则不进行实例化。

实例讲解

下面我们先通过一个实例来演示一下@Conditional注解的具体使用。了解了@Conditional的基本功能和使用场景,要实现具体的例子,大概可分为两步:第一,实现Condition接口定义实例化场景;第二,在具体的业务场景中使用@Conditional注解并指定其属性为上面的实现类。

首先定义一个简单的模型来实现:配置文件中可以指定数据库的类型,当指定不同的类型时,实例化不同的Bean。

配置文件中配置属性如下:

db.type=mysql

针对该配置文件定义Mysql和Oracle的Condition条件判断实现。

Mysql的Condition实现:

public class MysqlCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String dbType = context.getEnvironment().getProperty("db.type");
        return "mysql".equalsIgnoreCase(dbType);
    }

}

Oralce的Condition实现:

public class OracleCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String dbType = context.getEnvironment().getProperty("db.type");
        return "oracle".equalsIgnoreCase(dbType);
    }

}

两个Condition实现很简单,从环境变量中获得指定配置属性值,如果匹配对应的字符串,则表示符合条件并返回。

下面针对上面的定义进行使用,为了方便调用我们让对应的Mysql和Oracle配置实现均实现同一个接口ConfigBean。

public interface DbConfigBean {
    void printInfo();
}

具体实现如下:

public class MysqlDbConfigBean implements DbConfigBean {

    @Override
    public void printInfo(){
        System.out.println("I am Mysql!");
    }
}
public class OracleDbConfigBean implements DbConfigBean {

    @Override
    public void printInfo(){
        System.out.println("I am Oracle!");
    }
}

也就是说这两个实现类,都有打印对应Bean的信息的方法。

此时通过@Conditional来对这两个Bean的实例化进行注解判断。

@Configuration
public class DbConfig {

    @Bean
    @Conditional(MysqlCondition.class)
    public DbConfigBean mysql() {
        return new MysqlDbConfigBean();
    }

    @Bean
    @Conditional(OracleCondition.class)
    public DbConfigBean oracle() {
        return new OracleDbConfigBean();
    }
}

在配置类DbConfig中,定义了实例化DbConfigBean的两个方法,每个方法都通过@Conditional指定实例化的条件,当满足条件时就会创建对应的Bean。

注意此处两个Bean的实例化是互斥的,如果在实践过程中两个Bean有可能同时存在则使用@Primary注解来定义同时存在时使用哪个。

至此,@Conditional的定义和使用便完成了,下面看对具体Bean的使用和单元测试。

具体使用示例:

@Service
public class DbService {

    @Autowired
    private DbConfigBean configBean;

    public void getDbInfo(){
        configBean.printInfo();
    }
}

单元测试示例:

@SpringBootTest
class DbServiceTest {

    @Resource
    private DbService dbService;

    @Test
    void getDbInfo() {
        dbService.getDbInfo();
    }
}

此时根据配置文件中配置的是mysql还是oracle会调用对应的方法进行打印。

我们这里是针对指定的Bean使用@Conditional,如果一个@Configuration类被标注为@Conditional,则该类的所有的@Bean方法, @Import注解和@ComponentScan注解,都依从于该条件。

@Conditional衍生注解

@Conditional由Spring提供,而在Spring Boot中衍生出了以下相关的注解:

  • @ConditionalOnBean:当容器中有指定Bean的条件下。
  • @ConditionalOnClass:当classpath类路径下有指定类的条件下。
  • @ConditionalOnCloudPlatform:当指定的云平台处于active状态时。
  • @ConditionalOnExpression:基于SpEL表达式的条件判断。
  • @ConditionalOnJava:基于JVM版本作为判断条件。
  • @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。
  • @ConditionalOnMissingBean:当容器里没有指定Bean的条件。
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下。
  • @ConditionalOnNotWebApplication:当项目不是一个Web项目的条件下。
  • @ConditionalOnProperty:当指定的属性有指定的值的条件下。
  • @ConditionalOnResource:类路径是否有指定的值。
  • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean。
  • @ConditionalOnWebApplication:当项目是一个Web项目的条件下。

以上组合注解均位于spring-boot-autoconfigure jar包下的org.springframework.boot.autoconfigure.condition包下。



Spring Boot2.x系列教程(七)条件注解@Conditional详解及实战插图

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:https://choupangxia.com/2019/12/28/spring-boot2-x-conditional/