0%

springboot中conditional的使用

说明

在 springboot 中一个接口有多个实现,我们希望通过配置来控制运行时实例化哪个对象,springboot 中 @Conditional 注解可以帮助我们细粒度控制 bean 的实例化。

Spring Bootorg.springframework.boot.autoconfigure.condition 包下定义了以下注解:

注解名 作用
@ConditionalOnJava 基于JVM版本作为判断条件.
@ConditionalOnBean 当容器中有指定的Bean的条件下.
@ConditionalOnClass 当类路径下游指定的类的条件下.
@ConditionalOnExpression 基于SpEL表达式作为判断条件.
@ConditionalOnJndi 在JNDI存在的条件下查找指定的位置.
@ConditionalOnMissingBean 当容器中没有指定Bean的情况下.
@ConditionalOnMissingClass 当类路径下没有指定的类的情况下.
@ConditionalOnNotWebApplication 当前项目不是web项目的条件下.
@ConditionalOnProperty 指定的属性是否有指定的值.
@ConditionalOnResource 类路径是否有指定的值.
@ConditionalOnSingleCandidate 当指定Bean在容器中只有一个,或者虽然有多个但是指定首选的Bean.
@ConditionalOnWebApplication 当前项目是web项目的条件下.

@ConditionalOnExpression

@Component
// user.label 属性等于 user2 创建TestBean
// @ConditionalOnExpression("'${user.label}'.equals('user2')")

// 可以使用 && || 等运算符
// @ConditionalOnExpression("'${user.label}'.equals('user3') && '${xxx}'.equals('xx')")
// @ConditionalOnExpression("'${user.label}'.equals('${xxx}')")

// 当环境变量 user.label=user2 创建TestBean, 注意: 这个环境变量可以通过 application.yml 传入
@ConditionalOnExpression("#{'user2'.equals(environment['user.label'])}")
public class TestBean {
    @PostConstruct
    public void init() {
        System.out.println(111);
    }
}

@ConditionalOnProperty

@Component
// 当有 user.label 这个属性时创建 TestBean
// @ConditionalOnProperty(name = "user.label")
// 当有 user.label 和 xxx 两个属性时创建 TestBean
// @ConditionalOnProperty(name = {"user.label", "xxx"})
// 当有 user.label 这个属性且值为 user2 时创建 TestBean
@ConditionalOnProperty(name = "user.label", havingValue = "user2")
public class TestBean {
    @PostConstruct
    public void init(){
        System.out.println(111);
    }
}

自定义 Conditional

Bean对象

  • 两个 User Bean
@Data
@NoArgsConstructor
public class User1 {
    private String name;
    private int age;
}

@Data
@NoArgsConstructor
public class User2 {
    private String name;
    private int age;
}

方法一

  • 为每类对象创建一个 Conditional,如下为 User1和User2分别创建一个
public class MyConditional1 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String v = context.getEnvironment().getProperty("user.label");
        if (v.equals("user1")) return true;
        return false;
    }
}

public class MyConditional2 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String v = context.getEnvironment().getProperty("user.label");
        if (v.equals("user2")) return true;
        return false;
    }
}
  • springboot 实例化 bean
@Bean
@Conditional(MyConditional1.class)
public User init(){
    System.out.println(111);
    return new User();
}

@Bean
@Conditional(MyConditional2.class)
public User2 init2(){
    System.out.println(222);
    return new User2();
}
  • application.propeties
# 如果 label 匹配 bean 的label,该 bean 就会被实例化
user.label=user1

方法二

  • 新增注解给要实例化的对象打上标签
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(MyConditional.class)
public @interface MyConditionalAnnotation {
    String label();
}
  • MyConditional.java
public class MyConditional extends SpringBootCondition {
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(MyConditionalAnnotation.class.getName());
        // 获取注解所传入的 label
        Object value = annotationAttributes.get("label");
        if (value == null) {
//            return new ConditionOutcome(false, "ERROR");
            return ConditionOutcome.noMatch("ERROR");
        }
        // user.label 的值是通过application.propeties传入
        String v = context.getEnvironment().getProperty("user.label");
        if (v.equals(value)){
            // 如果匹配就实例化该 bean
            return ConditionOutcome.match("OK");
        }
        return ConditionOutcome.noMatch("ERROR");
    }
}

注意:代码中通过 application.propeties 传入配置优先级比较高,所以通过context可以获取到,如果通过别的配置文件可能无法获取则需要手动加载。

Properties properties = new Properties();
try {
    properties.load(conditionContext.getResourceLoader().getResource("test.properties").getInputStream());
} catch (IOException ex) {
    ex.printStackTrace();
}
String v = properties.getProperty("user.label");
  • springboot 创建bean
// 通过自定义注解设置 label
@MyConditionalAnnotation(label = "MyUserLabel1")
@Bean
public User1 init(){
    System.out.println(111);
    return new User1();
}

@MyConditionalAnnotation(label = "MyUserLabel2")
@Bean
public User2 init2(){
    System.out.println(222);
    return new User2();
}
  • application.propeties
# 如果 label 匹配 bean 的label,该 bean 就会被实例化
user.label=MyUserLabel2