Spring Framework 条件装配 之 @Conditional

Spring Framework 条件装配 之 @Conditional

前言java

了解SpringBoot的小伙伴对Conditional注解必定不会陌生,在SpringBoot项目中,Conditional注解被普遍的使用以及扩展出了许多Condition派生注解。虽然Conditional在SpringBoot中被丰富了不少,但它是在Spring Framework 4.0中提出的,因此本文仍是以Spring Framework 为基础进行讲解。spring

推荐阅读 黑色的眼睛 の 我的博客 Spring Framework 组件注册 之 FactoryBean Spring Framework 组件注册 之 @Import Spring Framework 组件注册 之 @Componentmarkdown

@Conditional 说明

要使用@Conditional注解,必须先了解一下Conditiona接口,它与@Conditional注解配合使用,经过源码咱们也能够看出,使用@Conditional注解必需要指定实现Conditiona接口的class。app

@Target({ElementType.TYPE, ElementType.METHOD})
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();
}
复制代码

Conditiona接口中,只定义了一个方法matches,spring在注册组件时,也正是根据此方法的返回值TRUE/FALSE来决定是否将组件注册到spring容器中学习

@FunctionalInterface
public interface Condition {
	/** * Determine if the condition matches. * @param context 条件判断的上下文环境 * @param metadata 正在检查的类或方法的注解元数据 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
复制代码

matches中咱们能够获取到ConditionContext接口,根据此接口对象能够获取BeanDefinitionRegistryConfigurableListableBeanFactory等重要对象信息,根据这些对象就能够获取和检查spring容器初始化时所包含的全部信息,再结合业务需求,就能够实现组件注册时的自定义条件判断。测试

@Conditional 使用

首先定义两个普通的JavaBean类spa

@Data
public class Test {
    private String id = "@Bean";
}

@Data
public class Test2 {
    private String id = "@Conditional";
}
复制代码

经过配置类和@Bean注解,向spring容器中注册组件3d

/** * spring组件配置类 */
@Configuration
public class TestConfiguration {
    /** * 向spring容器中注册Test 类型下beanName为test的组件 */
    @Bean
    public Test test() {
        return new Test();
    }

    /** * 根据TestCondition接口的条件判断向spring容器中注册Test2组件 */
    @Bean
    @Conditional(TestCondition.class)
    public Test2 test2() {
        return new Test2();
    }
}
复制代码

自定义实现Condition接口code

public class TestCondition implements Condition {
    /** * 当IOC容器中包含 Test类的bean定义信息时,条件成立 * * @param context 条件判断的上下文环境 * @param metadata 正在检查的类或方法的元数据 * @return 条件是否成立 */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
        return ArrayUtils.isNotEmpty(testBeanNames);
    }
}
复制代码

添加spring容器启动引导类component

/** * spring 容器启动引导类,测试 @Conditional 功能 */
@ComponentScan("com.spring.study.ioc.condition")
public class TestConditionalBootstrap {
    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestConditionalBootstrap.class);
        String[] names = applicationContext.getBeanNamesForType(Test.class);
        System.out.println("---Test bean names : " + Arrays.asList(names));
        names = applicationContext.getBeanNamesForType(Test2.class);
        System.out.println("---Test2 bean names : " + Arrays.asList(names));

        applicationContext.close();
    }
}
复制代码

运行spring引导类,控制台打印结果:

---hasTestBean : true ---Test bean names : [test] ---Test2 bean names : [test2]

由结果能够看出,Test正常注册到了spring容器中,知足了TestCondition接口的条件,全部Test2 也被注册到了spring容器中,为了进一步验证结果,咱们将Test组件删除掉,仅保留Test2 的注册,修改配置类以下

/** * spring组件配置类,将Test组件删除掉 */
@Configuration
public class TestConfiguration {
    /** * 根据TestCondition接口的条件判断向spring容器中注册Test2组件 */
    @Bean
    @Conditional(TestCondition.class)
    public Test2 test2() {
        return new Test2();
    }
}
复制代码

从新运行spring引导类,控制台打印结果以下:

---hasTestBean : false ---Test bean names : [] ---Test2 bean names : []

因而可知,当Test类在spring容器中没有注册时,不知足TestCondition接口条件,因此Test2 组件也不会被注册到spring容器中。此时若是将test2()注册组件上的@Conditional组件删除,Test2组件又会被正常注册到spring容器中。

上面的例子中是将@Conditional注解添加到了方法上此时条件仅对当前方法生效,@Conditional注解也能够加在上,此时条件对整个类中的组件注册均生效。按照上面的案例,作出如下调整:

  • TestCondition须要实现ConfigurationCondition接口,用来对配置类作处理

当配置类上添加了@Conditional注解时,须要注意的是,Condition接口中的条件是控制配置类自己仍是控制配置类中的全部组件,所以Spring Framework提供了ConfigurationCondition接口,并使用枚举值让咱们自定义选择。

enum ConfigurationPhase {

	/** * Condition接口中的条件控制着配置类自己的注册,当条件不匹配时,不会添加@configuration类 */
	PARSE_CONFIGURATION,

	/** * 控制Condition接口中的条件是对配置类中的组件进行解析,不会影响配置类自己的注册 */
	REGISTER_BEAN
}
复制代码
  • TestCondition接口实现修改以下
public class TestCondition implements ConfigurationCondition {
    /** * 当IOC容器中包含 Test的bean定义信息时,条件成立 * * @param context 条件判断的上下文环境 * @param metadata 正在检查的类或方法的元数据 * @return 条件是否成立 */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        String[] testBeanNames = beanFactory.getBeanNamesForType(Test.class);
        boolean hasTestBean = ArrayUtils.isNotEmpty(testBeanNames);
        System.out.println("---hasTestBean : " + hasTestBean);
        return hasTestBean;
    }

    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN;
    }
}
复制代码
  • 对javaBean类和配置类进行调整
@Data
@Component // 经过@Component注解直接注册 Test 组件
public class Test {
    private String id = "@Bean";
}

@Data
public class Test2 {
    private String id = "Test2: @Conditional";
}

@Data
public class Test3 {
    private String id = "Test3: @Conditional";
}
复制代码
  • 配置类调整以下,在配置类上添加@Conditional注解
/** * spring组件配置类,根据TestCondition接口的条件判断向spring容器中注册Test2,Test3组件 */
@Configuration
@Conditional(TestCondition.class)
public class TestConfiguration {
    @Bean
    public Test3 test3() {
        return new Test3();
    }

    @Bean
    public Test2 test2() {
        return new Test2();
    }
}
复制代码
  • 引导类中添加对Test3类型的查询
String[] names = applicationContext.getBeanNamesForType(Test3.class);
System.out.println("---Test3 bean names : " + Arrays.asList(names));
复制代码

启动引导类,控制台打印结果以下:

---hasTestBean : true ---Test bean names : [test] ---Test2 bean names : [test2] ---Test3 bean names : [test3]

因而可知,当TestCondition接口条件匹配时,Test2,Test3均被注册到spring容器中,若是将Test组件不进行注册,咱们看看下面的结果。

  • Test类上的@Component注解删除,其他代码均不变
@Data
public class Test {
    private String id = "@Bean";
}
复制代码

从新启动引导类,打印结果以下:

---hasTestBean : false ---Test bean names : [] ---Test2 bean names : [] ---Test3 bean names : []

由此能够看出,TestCondition的条件控制着配置类中的组件注册

使用 @Conditional 的注意点

  • @Conditional注解加在方法上时,能够直接使用Condition接口进行实现,经过条件匹配,判断组件是否能够被注册到spring容器中
  • @Conditional注解加在配置类上时,须要使用ConfigurationCondition接口进行实现,经过ConfigurationPhase来指定条件匹配对配置类自己注册的影响。由于Condition接口的条件是在spring扫描候选组件的过程当中执行的,因此在根据Bean进行条件判断时,须要注意此问题。若是是自定义的业务需求判断,不会受此影响。

总结

咱们日常在使用spring或者spring MVC时,@Conditional 注解的使用可能并非不少,可是在当下Spring Boot大行其道,而且Spring Boot对@Conditional进行了不少的扩展,因此了解@Conditional的使用及原理,也是对Spring Boot的基础学习作更多的铺垫。 本文对@Conditional的使用进行了介绍,没有深刻说明Condition的原理,这些内容将在后续的spring组件扫描过程当中进行说明。

学习永远都不是一件简单的事情,能够有迷茫,能够懒惰,可是前进的脚步永远都不能中止。

不积跬步,无以致千里;不积小流,无以成江海;

黑色的眼睛 の 我的博客

相关文章
相关标签/搜索