@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接口的定义:
@FunctionalInterfacepublic 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的实例化进行注解判断。
@Configurationpublic 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的使用和单元测试。
具体使用示例:
@Servicepublic class DbService { @Autowired private DbConfigBean configBean; public void getDbInfo(){ configBean.printInfo(); }}
单元测试示例:
@SpringBootTestclass 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包下。