( 十一 )SpringBoot @Conditional 条件注解
1、简介
SpringBoot 大量的自动化配置都是基于条件注解来实现的, 如果用户有配置就用用户的配置, 如果没有就用系统默认的配置。条件注解是整个 Spring Boot 的核心,条件注解并非一个新事物,这是一个存在于 Spring 中的东西,我们在 Spring 中常用的 profile 实际上就是条件注解的一个特殊化。
@Conditional 是 Spring4 新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean,让我们可以在满足不同条件时创建不同的 Bean,这种配置方式在 Spring Boot 中得到了广泛的使用,大量的自动化配置都是通过条件注解来实现的
有的小伙伴可能没用过条件注解,但是开发环境、生产环境切换的 Profile 多多少少都有用过吧?实际上这就是条件注解的一个特例。
2、@Conditional的定义:
//此注解可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
标注在类方法上: 一个方法只能注入一个bean实例,所以@Conditional标注在方法上只能控制一个bean实例是否注入。
标注在类上: 一个类中可以注入很多实例,@Conditional标注在类上就决定了一批bean是否注入
从代码中可以看到,需要传入一个Class数组,并且需要继承Condition接口:
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
Condition是个接口,需要实现matches方法,返回true则注入bean,false则不注入。
3、用法实践
1、定义接口Person
public interface Person {
String showName();
}
2、Person接口有一个 showName 方法 和 两个实现类
public class YellowPerson implements Person{
public String showName() {
return "中国人";
}
}
public class BlackPerson implements Person{
public String showName() {
return "黑人";
}
}
分别是 YellowPerson 和 BlackPerson两个类,两个类实现了 showName 方法,然后分别返回不同值
3、接下来再分别创建 YellowPersonCondition 和 BlackPersonCondition的条件类,如下:
public class YellowPersonCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("people").equals("中国人");
}
}
public class BlackPersonCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("people").equals("黑人");
}
}
在 matches 方法中做条件属性判断,当系统属性中的 people 属性值为 ‘中国人’ 的时候,YellowPersonCondition 的条件得到满足,当系统中 people 属性值为 ‘黑人’ 的时候,BlackPersonCondition 的条件得到满足,换句话说,哪个条件得到满足,一会就会创建哪个 Bean。
4、接下来我们来配置 配置类
@Configuration
public class JavaConfig {
@Bean("person")
@Conditional(YellowPersonCondition.class)
Person Yellow() {
return new YellowPerson();
}
@Bean("person")
@Conditional(BlackPersonCondition.class)
Person black() {
return new BlackPerson();
}
}
这个配置类,大家重点注意两个地方:
- 两个 Bean 的名字都为 person,这不是巧合,而是有意取的。两个 Bean 的返回值都为其父类对象 Person。
- 每个 Bean 上都多了 @Conditional 注解,当 @Conditional 注解中配置的条件类的 matches 方法返回值为 true 时,对应的 Bean 就会生效
5、测试:
public class Test{
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().getSystemProperties().put("people", "中国人");
ctx.register(JavaConfig.class);
ctx.refresh();
Person person= (Person) ctx.getBean("person");
System.out.println(person.showName());
}
}
4、@Profile 多环境配置注解
@Configuration
public class JavaConfig {
@Bean("person")
@Profile("中国人")
Person yellowPerson() {
return new YellowPerson();
}
@Bean("person")
@Profile("黑人")
Person blackPerson() {
return new BlackPerson();
}
}
这次不需要条件注解了,取而代之的是 @Profile 。然后在 Test方法中,按照如下方式加载 Bean:
public class Test{
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("中国人");
ctx.register(JavaConfig.class);
ctx.refresh();
Person person= (Person) ctx.getBean("person");
System.out.println(person.showName());
}
}
效果和上面的案例一样。
这样看起来 @Profile 注解貌似比 @Conditional 注解还要方便,那么 @Profile 注解到底是什么实现的呢?
我们来看一下 @Profile 的定义:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
可以看到,它也是通过条件注解来实现的。条件类是 ProfileCondition ,我们来看看:
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
看到这里就明白了,其实还是我们在条件注解中写的那一套东西,只不过 @Profile 注解自动帮我们实现了而已。
@Profile 虽然方便,但是不够灵活,因为具体的判断逻辑不是我们自己实现的。而 @Conditional 则比较灵活
4、@Conditional 派生注解的形式体现,如下表
注解 | 生效条件 |
@ConditionalOnJava | 应用使用指定的 Java 版本时生效 |
@ConditionalOnBean | 容器中存在指定的 Bean 时生效 |
@ConditionalOnMissingBean | 容器中不存在指定的 Bean 时生效 |
@ConditionalOnExpression | 满足指定的 SpEL 表达式时生效 |
@ConditionalOnClass | 存在指定的类时生效 |
@ConditionalOnMissingClass | 不存在指定的类时生效 |
@ConditionalOnSingleCandidate | 容器中只存在一个指定的 Bean 或这个 Bean 为首选 Bean 时生效 |
@ConditionalOnProperty | 系统中指定属性存在指定的值时生效 |
@ConditionalOnResource | 类路径下存在指定的资源文件时生效 |
@ConditionalOnWebApplication | 当前应用是 web 应用时生效 |
@ConditionalOnNotWebApplication | 当前应用不是 web 应用生效 |