条件注解-@conditional

1,183 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 11 天,点击查看活动详情

日积月累,水滴石穿 😄

前言

@conditional中文意思为有条件的。的确它也是干这事的。

@Conditional是 Spring4 提供的注解,作用是按照指定的条件进行判断,满足所有条件候选Bean才会向容器注册。

源码定义

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition Conditions} that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

该注解可以在类、方法上标注。注解中就一个 value属性,值为继承Condition的接口的数组。

Condition 接口定义如下:

@FunctionalInterface
public interface Condition {

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
	 * or {@link org.springframework.core.type.MethodMetadata method} being checked
	 * @return {@code true} if the condition matches and the component can be registered,
	 * or {@code false} to veto the annotated component's registration
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

Condition 是一个函数式接口,因此可以用 lambda 表达式或方法引用为目标赋值。实现 Condition接口,需要重写 matches方法,返回 true 则表示判断条件匹配成功,注入bean,为 false 则不注入。

测试

  • 提供两个 Bean,分别为UserServiceImplProductServiceImpl
@Configuration
public class BeanConfig {

    @Bean
    public UserServiceImpl u(){
        UserServiceImpl userService = new UserServiceImpl();
        System.out.println("u = " + userService);
        return userService;
    }

    @Bean
    public ProductServiceImpl p(){
        ProductServiceImpl productService = new ProductServiceImpl();
        System.out.println("p = " + productService);
        return productService;
    }
}
  • 启动类
@ComponentScan(basePackages = "com.cxyxj.conditional")
public class AppMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMain.class);

        // 打印 bean 名称
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String name : beanDefinitionNames){
            System.out.println(name);
        }
    }
}
  • 启动结果

image.png

可以发现两个 Bean,都注册到容器中了。前戏备足了,接下来进入正戏。如果现在我不想将两个 Bean 同时注入进来,我希望由一个标志位来控制,如果标志位为 true,则注入 UserServiceImpl,标志位为 false、或者为空,则注入 ProductServiceImpl

自定义 Condition

UserCondition

public class UserCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("enable.flag");
        //如果 enable.flag 的值为 true
        if(Boolean.parseBoolean(property)){
            return true;
        }
        return false;
    }

}

ProductCondition

public class ProductCondition implements Condition {

    /**
     *
     * @param context:可以获得Bean工厂、Environment、bean定义注册器、ClassLoader、ResourceLoader
     * @param metadata:可以获得注解信息,比如:方法名称、
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("enable.flag");
        //如果 enable.flag 的值为 false 或者为空
        if(!Boolean.parseBoolean(property)){
            return true;
        }
        return false;
    }

}

标注在方法上

标注在方法上,一个自定义的 Condition只能控制一个 Bean 实例是否需要注入 。

在两个 @Bean方法添加我们自定义的 Condition

//标志位为 true,则注入 `UserServiceImpl`
@Bean
@Conditional({UserCondition.class})
public UserServiceImpl u(){
    UserServiceImpl userService = new UserServiceImpl();
    System.out.println("u = " + userService);
    return userService;
}

//标志位为 false、或者为空,则注入 `ProductServiceImpl`。
@Bean
@Conditional({ProductCondition.class})
public ProductServiceImpl p(){
    ProductServiceImpl productService = new ProductServiceImpl();
    System.out.println("p = " + productService);
    return productService;
}
  • 启动结果

image.png

注入了ProductServiceImpl,这是由于 enable.flag还没提供,获得的值为 null。符合ProductCondition的匹配规则。

接下来提供 enable.flag=true。这里就懒得创建配置文件了,直接加在启动参数上。

image.png

由于我这是 Spring 的项目哈,没办法配置 VM options 变量,配置 Environment variables 变量也是一样的,格式为 key= value。

image.png

  • 启动结果

image.png

标注在类上

@Conditional注解还可以标注在类上,标注在类上代表着这类中所有的被 @Bean的方法,对需要进行条件匹配,可以进行批量注入。

  • 修改BeanConfig@Conditional注解的值为UserCondition,也就代表着 enable.flag=trueUserServiceImplProductServiceImpl都会被注入。
@Configuration
@Conditional({UserCondition.class})
public class BeanConfig {

    @Bean
    public UserServiceImpl u(){
        UserServiceImpl userService = new UserServiceImpl();
        System.out.println("u = " + userService);
        return userService;
    }

    @Bean
    public ProductServiceImpl p(){
        ProductServiceImpl productService = new ProductServiceImpl();
        System.out.println("p = " + productService);
        return productService;
    }
}
  • 启动结果

image.png

@Conditional注解可以传入一个 Class 数组,也就是可以多个条件匹配。比如在方法、类上进行标注,需要所有的条件都返回 true,才会进行注入。


  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。