This post is also available in English and alternative languages. Spring Framework版本:5.1.6.RELEASE (点击跳转官方文档) SpringBoot版本:2.1.4.RELEASE (点击跳转官方文档)
Spring Framework 条件装配 Conditional,作用是按照一定的条件进行判断,满足条件再向容器注册Bean。
1. Conditional注解 @Conditional 注解是在 Spring Framework 4.0 中提出的,为了测试方便,以下示例代码使用 SpringBoot 运行。
2. 前言 要使用 @Conditional
注解,必须先了解一下 Conditiona
接口,它与 @Conditional
注解配合使用,通过源码可以发现,使用 @Conditional
注解必须要指定 Conditiona
接口的实现类。
1 2 3 4 5 6 7 8 9 10 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { Class<? extends Condition >[] value(); }
在 Conditiona
接口中,只定义了一个 matches
方法,Spring 在注册时,会根据此方法的 Boolean 返回值决定是否将组件注册到容器中。
1 2 3 4 5 6 7 8 9 10 11 12 @FunctionalInterface public interface Condition { boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) ; }
在 matches
中可以获取到 ConditionContext
接口,从此接口对象中可以获取到 BeanDefinitionRegistry
、 ConfigurableListableBeanFactory
等对象信息,从这些对象中就可以获取、检查容器初始化时所包含的所有信息,结合需求就可以实现组件注册时的自定义条件判断。
3. 作用在方法上 作用在方法上,能决定该单个方法返回的实例是否加载到容器。
首先创建两个普通的 Bean,通过配置类和使用 @Bean
注解,向容器中注册。
1 2 3 4 5 @Data public class Computer { private String id = "我是电脑..." ; }@Data public class Job { private String id = "我是工作..." ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration public class Module01Configuration { @Bean public Computer createComputer () { return new Computer (); } @Bean @Conditional(value = CustomizeConditional.class) public Job createJob () { return new Job (); } }
接着实现 Condition
接口,在其中自定义逻辑:当 Computer 类存在时,返回 true,即创建 Job 类;如果 Computer 不存在,则返回 false,即不创建 Job 类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Slf4j public class CustomizeConditional implements Condition { @Override public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); String[] computerBeanNames = beanFactory.getBeanNamesForType(Computer.class); if (ArrayUtils.isNotEmpty(computerBeanNames)) { log.info(">>>>> 存在Computer, 允许创建Job,computerBeanNames:[{}]" , JsonUtil.beanToJson(computerBeanNames)); }else { log.info(">>>>> 不存在Computer, 不允许创建Job" ); } return ArrayUtils.isNotEmpty(computerBeanNames); } }
运行 SpringBoot 启动类进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Slf4j @SpringBootApplication public class ApplicationSpringBootExample12 { public static void main (String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(ApplicationSpringBootExample12.class, args); String[] computerArr = applicationContext.getBeanNamesForType(Computer.class); log.info(">>>>> Computer bean names:[{}]" , Arrays.asList(computerArr)); String[] jobArr = applicationContext.getBeanNamesForType(Job.class); log.info(">>>>> Job bean names:[{}]" , Arrays.asList(jobArr)); } }
运行结果:
1 2 3 >>>>> 存在Computer, 允许创建Job,computerBeanNames:[["createComputer"]] >>>>> Computer bean names:[[createComputer]] >>>>> Job bean names:[[createJob]]
从运行结果可以看出,Computer 类正常注册到容器中,满足 CustomizeConditional 中的判断逻辑,所以 Job 类也被注册到容器中。
修改下 Module01Configuration 类,将其中创建 Computer 的代码删除,即容器中不存在 Computer,看看结果如何。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration public class Module01Configuration { @Bean @Conditional(value = CustomizeConditional.class) public Job createJob () { return new Job (); } }
运行结果:
1 2 3 >>>>> 不存在Computer, 不允许创建Job >>>>> Computer bean names:[[]] >>>>> Job bean names:[[]]
从运行结果可以看出,容器中没有 Computer 类,不满足 CustomizeConditional 中的判断逻辑,所以 Job 类也不会被注册到容器中。
4. 作用在类上 @Conditional
注解也可以加在类上,条件对整个类中的组件注册均生效。
先分别创建 Computer、Job、Leader 三个类,其中 Computer 类通过 @Component
注解直接注册到容器中。
1 2 3 4 5 6 7 8 9 @Data @Component public class Computer { private String id = "我是电脑..." ; }@Data public class Job { private String id = "我是工作..." ; }@Data public class Leader { private String id = "我是leader..." ; }
然后 CustomizeConditional 类需要实现 ConfigurationCondition
接口,在其中自定义逻辑:当 Computer 类存在时,返回 true,即创建 Job、Leader 类;如果 Computer 不存在,则返回 false,即不创建 Job、Leader 类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Slf4j public class CustomizeConditional implements ConfigurationCondition { @Override public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); String[] computerBeanNames = beanFactory.getBeanNamesForType(Computer.class); if (ArrayUtils.isNotEmpty(computerBeanNames)) { log.info(">>>>> 存在Computer, 允许创建Job和Leader,computerBeanNames:[{}]" , JsonUtil.beanToJson(computerBeanNames)); } else { log.info(">>>>> 不存在Computer, 不允许创建Job和Leader" ); } return ArrayUtils.isNotEmpty(computerBeanNames); } @Override public ConfigurationPhase getConfigurationPhase () { return ConfigurationPhase.REGISTER_BEAN; } }
Spring Framework 提供了 ConfigurationCondition 接口,并提供枚举值,让我们自己选择判断条件控制 配置类本身 还是控制 配置类中的所有组件 。
接着通过配置类 Module02Configuration ,将 Job、Leader 注入到容器中,同时在 Module02Configuration 类上添加 @Conditional
注解,即:当 CustomizeConditional 类中的判断条件满足,才会创建配置类中的其他Bean。
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration @Conditional(CustomizeConditional.class) public class Module02Configuration { @Bean public Job createJob () { return new Job (); } @Bean public Leader createLeader () { return new Leader (); } }
运行 SpringBoot 启动类进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Slf4j @SpringBootApplication public class ApplicationSpringBootExample12 { public static void main (String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(ApplicationSpringBootExample12.class, args); ApplicationSpringBootExample12 contextBean = applicationContext.getBean(ApplicationSpringBootExample12.class); contextBean.runModule02(applicationContext); } private void runModule02 (ConfigurableApplicationContext applicationContext) { String[] computerArr = applicationContext.getBeanNamesForType(com.yxcheng.example12.module02.domain.Computer.class); log.info(">>>>> Computer bean names:[{}]" , Arrays.asList(computerArr)); String[] jobArr = applicationContext.getBeanNamesForType(com.yxcheng.example12.module02.domain.Job.class); log.info(">>>>> Job bean names:[{}]" , Arrays.asList(jobArr)); String[] leaderArr = applicationContext.getBeanNamesForType(Leader.class); log.info(">>>>> Leader bean names:[{}]" , Arrays.asList(leaderArr)); } }
运行结果:
1 2 3 4 >>>>> 存在Computer, 允许创建Job和Leader,computerBeanNames:[["computer"]] >>>>> Computer bean names:[[computer]] >>>>> Job bean names:[[createJob]] >>>>> Leader bean names:[[createLeader]]
从运行结果可以看出,Computer 类正常注册到容器中,满足 CustomizeConditional 中的判断逻辑,所以 Job、Leader 类也被注册到容器中。
修改下 Computer 类,将 @Component
注解删除,即:不向容器中注册 Computer,看下情况如何。
1 2 3 4 5 @Data @Component public class Computer { private String id = "我是电脑..." ; }
运行结果:
1 2 3 4 >>>>> 不存在Computer, 不允许创建Job和Leader >>>>> Computer bean names:[[]] >>>>> Job bean names:[[]] >>>>> Leader bean names:[[]]
由于 Computer 类没有注册到容器中,不满足 CustomizeConditional 中的判断逻辑,所以 Job、Leader 类没有被注册到容器中。
5. 依赖POM 以上示例的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-validation</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-freemarker</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-logging</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > <exclusions > <exclusion > <groupId > org.junit.vintage</groupId > <artifactId > junit-vintage-engine</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.22</version > </dependency > </dependencies >
6. Reference