( 十一 )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 应用生效