SpringBoot中的@Conditional注解

一、介绍

Spring的应用下,我们希望一些bean可以通过一些条件来判断是否需要实例化,并加载到spring容器中。

所以,@Conditional注解就是为了解决上面这个需求而制定的注解。@Conditional注解是总接口,可以定制逻辑。

二、详情

1)@Conditional

先看源码,此注解需要传入Condition接口的实现类,可以多个

1
2
3
4
5
6
7
8
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

Class<? extends Condition>[] value();

}

所以,使用此注解时,我们若是有些高度定制化的一些判断,可以先实现Condition接口,再讲实现类提供给@Conditional注解,使用示例如下。

首先要有一个Condition接口的实现类,可以看看框架中其他的实现类,这边自己实现一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.banmoon.test;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class TestCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO 定制化逻辑,比如说数据库的某项配置
return true;
}
}

再进行其进行判断并初始化bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootApplication
public class TestApplication {

@Bean("conditional")
@Conditional(TestCondition.class)
public String conditional() {
return "conditional";
}

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
String string = run.getBean("conditional", String.class);
System.out.println(string);
System.out.println(System.lineSeparator());
}
}

运行结果如下,成功获取到bean

image-20220712134735438

2)@ConditionalOnBean

@ConditionalOnBean@ConditionalOnMissingBean是相反对应的一组注解,看注解名称也可以看出来。

前者是判断存在某个bean,后者是判断是否确实某个bean

先来看看他们的使用,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@SpringBootApplication
public class TestApplication {

@Bean("conditionalOnBean")
@ConditionalOnBean(TestApplication.class)
public String conditionalOnBean() {
return "conditionalOnBean";
}

@Bean("conditionalOnMissingBean")
@ConditionalOnMissingBean(TestApplication.class)
public String conditionalOnMissingBean() {
return "conditionalOnMissingBean";
}

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
Map<String, String> beanMap = run.getBeansOfType(String.class);
beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
System.out.println(System.lineSeparator());
}

}

image-20220712140430447

可以看到只有一个bean被创建出来,另外一个由于不满足条件,所以没有创建bean


查看注解源码,发现除了可以传Class对象,还可以传其他的属性来进行确定

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
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {

/**
* 检查bean的class类型,当指定的所有类的 bean 都包含在 BeanFactory 中时,此条件才匹配
*/
Class<?>[] value() default {};

/**
* 检查bean的权限名,当指定的所有类的 bean 都包含在 BeanFactory 中时,此条件才匹配
*/
String[] type() default {};

/**
* 检查bean的注解类型,当指定的所有注解都在 BeanFactory 中的 bean 上定义时,此条件才匹配
*/
Class<? extends Annotation>[] annotation() default {};

/**
* 要检查的 bean 的名称。当指定的所有 bean 名称都包含在 BeanFactory 中时,此条件才匹配。
*/
String[] name() default {};

/**
* 考虑应用程序的上下文结构的策略
*/
SearchStrategy search() default SearchStrategy.ALL;

/**
* 可能在其通用参数中包含指定 bean 类型的其他类。例如,声明 value=Name.class 和 parameterizedContainer=NameRegistration.class 的注释将同时检测 Name 和 NameRegistration<Name>。
*/
Class<?>[] parameterizedContainer() default {};

}

3)@ConditionalOnProperty

看名字也就能看的出来,是指从配置文件加载,用来进行条件判断。使用如下

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
package com.banmoon.test;

import cn.hutool.core.util.StrUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

import java.util.Map;

@SpringBootApplication
public class TestApplication {

@Bean("conditionalOnProperty")
@ConditionalOnProperty(value = "conditionalOnProperty", prefix = "banmoon-condition")
public String conditionalOnProperty() {
return "conditionalOnProperty";
}

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
Map<String, String> beanMap = run.getBeansOfType(String.class);
beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
System.out.println(System.lineSeparator());
}

}

image-20220712163628496


还是点入进去看看该注解的源码

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
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

/**
* 同name属性一致
*/
String[] value() default {};

/**
* 应用于每个属性的前缀。如果未指定,前缀会自动以点结尾。有效前缀由一个或多个用点分隔的单词定义(例如“acme.system.feature”)
*/
String prefix() default "";

/**
* 要测试的属性的名称。如果已定义前缀,则将其应用于计算每个属性的完整键。例如,如果前缀是 app.config 并且一个值是 my-value,则完整的键将是 app.config.my-value 使用虚线表示法来指定每个属性,即全部小写,用“-”分隔单词(例如 my-long-property)
*/
String[] name() default {};

/**
* 属性预期值的字符串表示形式。如果未指定,则该属性不得等于 false
*/
String havingValue() default "";

/**
* 如果未设置属性,则指定条件是否应匹配。默认为false
*/
boolean matchIfMissing() default false;

}

还有一段不好翻译,给予截图吧,指的是havingValue属性的使用。

这些比较特殊,不同的属性值和不同的havingValue组合,可以得到什么样的结果。

image-20220712164718441

4)@ConditionalOnClass

@ConditionalOnClass@ConditionalOnMissingClass也是一组相对的注解,他们的功能是判断是否拥有java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@SpringBootApplication
public class TestApplication {

@Bean("conditionalOnClass")
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public String conditionalOnClass() {
return "conditionalOnClass";
}

@Bean("conditionalOnMissingClass")
@ConditionalOnMissingClass(value = "oracle.jdbc.driver.OracleDriver")
public String conditionalOnMissingClass() {
return "conditionalOnMissingClass";
}

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
Map<String, String> beanMap = run.getBeansOfType(String.class);
beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
System.out.println(System.lineSeparator());
}

}

由于我只添加了MySQL的驱动,没有加上Oracle,所以结果如下

image-20220712165840798

5)@ConditionalOnWebApplication

@ConditionalOnWebApplication@ConditionalOnNotWebApplication是一组相对的注解,指的判断是否是web环境应用。

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
package com.banmoon.test;

import cn.hutool.core.util.StrUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

import java.util.Map;

@SpringBootApplication
public class TestApplication {

@Bean("conditionalOnWebApplication")
@ConditionalOnWebApplication
public String conditionalOnWebApplication() {
return "conditionalOnWebApplication";
}

@Bean("conditionalOnNotWebApplication")
@ConditionalOnNotWebApplication
public String conditionalOnNotWebApplication() {
return "conditionalOnNotWebApplication";
}

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
Map<String, String> beanMap = run.getBeansOfType(String.class);
beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
System.out.println(System.lineSeparator());
}

}

image-20220713110100264

6)@ConditionalOnJava

@ConditionalOnJava用来判断当前java的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootApplication
public class TestApplication {

@Bean("conditionalOnJava")
@ConditionalOnJava(value = JavaVersion.EIGHT)
public String conditionalOnJava() {
return "conditionalOnJava";
}

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
Map<String, String> beanMap = run.getBeansOfType(String.class);
beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
System.out.println(System.lineSeparator());
}

}

image-20220713111005274

看下注解的源码,比较简单

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
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnJavaCondition.class)
public @interface ConditionalOnJava {

/**
* 配合value()使用,来确定范围。默认是Range.EQUAL_OR_NEWER,也就是比指定范围高
*/
Range range() default Range.EQUAL_OR_NEWER;

/**
* 指定Java版本
*/
JavaVersion value();

enum Range {

/**
* 大于等于
*/
EQUAL_OR_NEWER,

/**
* 低于
*/
OLDER_THAN

}

}

7) @ConditionalOnExpression

使用Spring表达式来进行判断,也就是SpEL表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SpringBootApplication
public class TestApplication {

@Bean("intValue")
public Integer intValue() {
return 10;
}

@Bean("conditionalOnExpression")
@ConditionalOnExpression("#{intValue>5}")
public String conditionalOnExpression() {
return "conditionalOnExpression";
}

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
Map<String, String> beanMap = run.getBeansOfType(String.class);
beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
System.out.println(System.lineSeparator());
}

}

image-20220713141743435

8)@ConditionalOnResource

@ConditionalOnResource是否有指定的静态资源,示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootApplication
public class TestApplication {

@Bean("conditionalOnResource")
@ConditionalOnResource(resources = "application.yml")
public String conditionalOnResource() {
return "conditionalOnResource";
}

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
Map<String, String> beanMap = run.getBeansOfType(String.class);
beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
System.out.println(System.lineSeparator());
}

}

image-20220713142800606

9)@ConditionalOnSingleCandidate

@ConditionalOnSingleCandidate,判断指定的类型是否只有一个bean,示例如下

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
@SpringBootApplication
public class TestApplication {

@Bean("intValue")
public Integer intValue(){
return 10;
}

@Bean("intValue2")
public Integer intValue2(){
return 20;
}

@Bean("doubleValue")
public Double doubleValue(){
return 10D;
}

@Bean("intValueConditional")
@ConditionalOnSingleCandidate(Integer.class)
public String intValueConditional() {
return "intValueConditional";
}

@Bean("doubleValueConditional")
@ConditionalOnSingleCandidate(Double.class)
public String doubleValueConditional() {
return "doubleValueConditional";
}

public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(TestApplication.class, args);
Map<String, String> beanMap = run.getBeansOfType(String.class);
beanMap.forEach((k, v) -> System.out.println(StrUtil.format("{}:{}", k, v)));
System.out.println(System.lineSeparator());
}

}

image-20220713154856742

三、最后

整理一下注解表格,其中有一些上面没有列出来的注解

注解 说明
@Conditional 传入一个Condition接口的实现类,来进行判断
@ConditionalOnBean 判断是否存在某个bean
@ConditionalOnMissingBean 判断是否缺失了某个bean
@ConditionalOnProperty 判断配置文件中的某项配置
@ConditionalOnClass 判断是否拥有某个java
@ConditionalOnMissingClass 判断是否缺失了某个java
@ConditionalOnWebApplication 判断当前是否为web环境
@ConditionalOnNotWebApplication 判断当前是否不为web环境
@ConditionalOnJava 判断当前java运行版本
@ConditionalOnExpression 使用Spring表达式来进行判断,也就是SpEL表达式
@ConditionalOnResource 判断是否有指定的静态资源
@ConditionalOnSingleCandidate 判断指定的类型是否只有一个bean
@ConditionalOnJndi 通过JNDI进行判断
@ConditionalOnCloudPlatform 判断当前环境是否是云平台
@ConditionalOnWarDeployment 判断当前是否是War包环境
@ConditionalOnMissingFilterBean 判断是否缺失了某个bean过滤器

我是半月,祝你幸福!!!