Conditional条件装配
2025-01-22 08:19:30    2.1k 字   
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 {
/**
* 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 在注册时,会根据此方法的 Boolean 返回值决定是否将组件注册到容器中。

1
2
3
4
5
6
7
8
9
10
11
12
@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);
}

matches 中可以获取到 ConditionContext 接口,从此接口对象中可以获取到 BeanDefinitionRegistryConfigurableListableBeanFactory 等对象信息,从这些对象中就可以获取、检查容器初始化时所包含的所有信息,结合需求就可以实现组件注册时的自定义条件判断。


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();
}
/**
* 根据 {@link CustomizeConditional} 类的条件判断,向容器中注册 Job。
*/
@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 {
/**
* 当容器中包含 {@link Computer} 类的 bean 定义信息时,条件成立
*
* @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 条件是否成立
*/
@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
// public Computer createComputer() {
// return new Computer();
// }
/**
* 根据 {@link CustomizeConditional} 类的条件判断,向容器中注册 Job。
*/
@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>
<!--starter-->
<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>

<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>

6. Reference