Spring Conditional详解

@ConditionalSpring4新提供的注解,它的作用是按照一定条件进行判断,满足条件就将bean注册到容器。

Contidional 介绍

Conditional是由SpringFramework提供的一个注解,位于 org.springframework.context.annotation 包内,定义如下。

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {

	Class<? extends Condition>[] value();

}

我们可以将 Spring@Conditional注解用于以下场景:

  • 可以作为类级别的注解直接或者间接的与@Component相关联,包括@Configuration类
  • 可以作为元注解,用于自动编写构造性注解;
  • 作为方法级别的注解,作用在任何@Bean方法上。

Condition 接口

Condition 将在即将注册 Bean 定义之前进行检查,并且可以根据它的 matches() 方法返回值动态决定是否注册组件(true-允许注册;false-不允许注册)。

1
2
3
public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

注意:Condition 必须遵循与 BeanFactoryPostProcessor 相同的限制,并注意永远不要与 bean 实例交互。对于与 @Configuration bean 交互的条件的更细粒度控制,请考虑实现 ConfigurationCondition 接口。

Contidional 申明

  • 某个@Configuration注解的配置类上
  • 某个@Configuration注解配置类中带有@Bean注解的方法上
  • 任何用@Component@Service@Repository@Controller注释声明的 Bean
1
2
3
4
5
@Configuration
@Conditional(IsDevEnvCondition.class)
class DevEnvLoggingConfiguration {
    // ...
}
1
2
3
4
5
6
7
8
9
@Configuration
class DevEnvLoggingConfiguration {
    
    @Bean
    @Conditional(IsDevEnvCondition.class)
    LoggingService loggingService() {
        return new LoggingService();
    }
}
1
2
3
4
5
@Service
@Conditional(IsDevEnvCondition.class)
class LoggingService {
    // ...
}

Contidional 实战

自定义条件

检查 Java 版本是否为 8

1
2
3
4
5
6
class Java8Condition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return JavaVersion.getJavaVersion().equals(JavaVersion.EIGHT);
    }
}

Java8Condition 作为 @Conditional 注释中的一个属性:

1
2
3
4
5
@Service
@Conditional(Java8Condition.class)
public class Java8DependedService {
    // ...
}

常见的条件注解

ConditionalOnProperty
  1. value属性,指定要检查的配置属性;
  2. haveValue属性,定义这个条件所需的值;
  3. matchIfMissing属性,如果缺少参数,是否应该匹配条件。
1
2
3
4
5
6
7
8
@Service
@ConditionalOnProperty(
  value="logging.enabled", 
  havingValue = "true", 
  matchIfMissing = true)
class LoggingService {
    // ...
}
ConditionalOnExpression

相比较前面的Bean,Class是否存在,配置参数是否存在或者有某个值而言,这个依赖SPEL表达式的,就显得更加的高级了; 其主要就是执行Spel表达式,根据返回的true/false来判断是否满足条件

1
2
3
4
5
6
7
@Service
@ConditionalOnExpression(
  "${logging.enabled:true} and '${logging.level}'.equals('DEBUG')"
)
class LoggingService {
    // ...
}
ConditionalOnBean

只有在容器中注册了某个bean,该条件才起作用

1
2
3
4
5
@Service
@ConditionalOnBean(CustomLoggingConfiguration.class)
class LoggingService {
    // ...
}

Contidional 进阶

多重注解条件

此处demo实现满足某个环境变量时该条件才能生效

创建注解类 Env

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

创建 Condition 实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class EnvCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
        // 查找@EnvCondition作用的方法或者类上的@Env注解
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(Env.class.getName());
        if (annotationAttributes != null) {
            String[] envList = (String[]) annotationAttributes.get("value");
            // 获取配置的环境变量,通过Java启动参数(vm options)配置 -Denv=dev
            String env = context.getEnvironment().getProperty("env");
            return Arrays.stream(envList).anyMatch(r -> Objects.equals(r, env));
        }
        return false;
    }
}

自定义条件使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Configuration
public class DemoConfiguration{
    @Env("dev")
    @EnvCondition
    public EnvBean envBean(){
        // ...
    }

    @Env("prod")
    @EnvCondition
    public EnvBean prodBean(){
        // ...
    }
}

写在最后

码字不易,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激