0%

SpringBoot之条件注解

这里介绍SpringBoot的@ConditionalOn系列注解,以及如何自定义实现条件注解

abstract.png

@ConditionalOn系列注解

对于@ConditionalOn系列注解而言

  • 当添加在Java配置类(即被@Configuration注解修饰的类)上后,则只有符合期望条件后,才会对该Java配置类进行实例化
  • 当添加在Java配置类中被@Bean注解修饰的方法上后,则只有符合期望条件后,才会调用该方法来创建相应的bean对象

常用地条件注解有:

  • @ConditionalOnClass注解:存在指定类时,才算符合条件
  • @ConditionalOnMissingClass注解:不存在指定类时,才算符合条件
  • @ConditionalOnBean注解:存在指定名称的bean时,才算符合条件
  • @ConditionalOnMissingBean注解:不存在指定名称的bean时,才算符合条件
  • @ConditionalOnProperty注解:其prefix属性用于指定检查的配置项的名称前缀;其name属性用于指定检查的配置项的名称;其havingValue属性用于指定配置项的期望值。具体地,如果配置项的值和该属性值相等,则条件成立;此外,当该属性配置为””空字符串,则意为相应配置项的值不为false时条件均成立;其matchIfMissing属性设置为true后,即使指定的配置项不存在,条件也会成立

测试:添加在类上

符合期望条件

这里我们将条件注解添加在Java配置类(即被@Configuration注解修饰的类)上,来测试符合期望条件的场景

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.context.annotation.Configuration;

// 该配置类会被实例化
@Configuration
public class MyConfig1 {
public MyConfig1() {
System.out.println("<My Config 1>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;

// 存在指定类时,该配置类才会被实例化
@ConditionalOnClass(name = "com.aaron.application.SpringSpi.ConditionalOnTest.StuInfo")
@Configuration
public class MyConfig2 {
public MyConfig2() {
System.out.println("<My Config 2>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Configuration;

// 不存在指定类时,该配置类才会被实例化
@ConditionalOnMissingClass("com.aaron.application.SpringSpi.ConditionalOnTest.StuInfo123")
@Configuration
public class MyConfig3 {
public MyConfig3() {
System.out.println("<My Config 3>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;

// 存在指定名称的bean时,该配置类才会被实例化
@ConditionalOnBean(name="stuInfo")
@Configuration
public class MyConfig4 {
public MyConfig4() {
System.out.println("<My Config 4>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Configuration;

// 不存在指定名称的bean时,该配置类才会被实例化
@ConditionalOnMissingBean(name="stuInfo123")
@Configuration
public class MyConfig5 {
public MyConfig5() {
System.out.println("<My Config 5>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

// 配置项 aaron.config.age 存在 且 配置值不为false 时,该配置类才会实例化
@ConditionalOnProperty(prefix = "aaron.config", name="age", havingValue = "", matchIfMissing = false)
@Configuration
public class MyConfig6 {
public MyConfig6() {
System.out.println("<My Config 6>: 被实例化");
}
}

同时为了测试各种条件,这里添加一个StuInfo类,同时添加了@Component注解。使其成为一个bean

1
2
3
4
5
6
7
package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.stereotype.Component;

@Component
public class StuInfo {
}

此外为了测试@ConditionalOnProperty注解,在配置文件application.properties中添加下述配置

1
2
3
...
aaron.config.age=14
...

测试结果如下所示,符合预期

figure 1.png

不符合期望条件

现在我们来修改下上述各条件注解上指定的条件,使其不符合期望条件

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.context.annotation.Configuration;

// 该配置类会被实例化
@Configuration
public class MyConfig1 {
public MyConfig1() {
System.out.println("<My Config 1>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;

// 由于不存在 StuInfo123 类,故该配置类不会被实例化
@ConditionalOnClass(name = "com.aaron.application.SpringSpi.ConditionalOnTest.StuInfo123")
@Configuration
public class MyConfig2 {
public MyConfig2() {
System.out.println("<My Config 2>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Configuration;

// 由于存在 StuInfo 类,故该配置类不会被实例化
@ConditionalOnMissingClass("com.aaron.application.SpringSpi.ConditionalOnTest.StuInfo")
@Configuration
public class MyConfig3 {
public MyConfig3() {
System.out.println("<My Config 3>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;

// 由于不存在 名为stuInfo123 的bean,故该配置类不会被实例化
@ConditionalOnBean(name="stuInfo123")
@Configuration
public class MyConfig4 {
public MyConfig4() {
System.out.println("<My Config 4>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Configuration;

// 由于存在 名为stuInfo 的bean,故该配置类不会被实例化
@ConditionalOnMissingBean(name="stuInfo")
@Configuration
public class MyConfig5 {
public MyConfig5() {
System.out.println("<My Config 5>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

// 由于配置项 aaron.config.age 的值不为996 时,故该配置类不会实例化
@ConditionalOnProperty(prefix = "aaron.config", name="age", havingValue = "996", matchIfMissing = false)
@Configuration
public class MyConfig6 {
public MyConfig6() {
System.out.println("<My Config 6>: 被实例化");
}
}

测试结果如下所示,符合预期

figure 2.png

测试:添加在方法上

这里我们将条件注解添加在Java配置类中被@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
package com.aaron.application.SpringSpi.ConditionalOnTest;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig7 {

public MyConfig7() {
System.out.println("<My Config 7>: 被实例化");
}

// 存在指定类时,方法被调用,该bean才会被创建实例化
@ConditionalOnClass(name = "com.aaron.application.SpringSpi.ConditionalOnTest.StuInfo")
@Bean
public Object buildObjA() {
System.out.println("bean A 被创建、实例化");
return new Integer(2);
}

// 由于不存在 StuInfo123 类,方法不会被调用,故该bean不会被创建实例化
@ConditionalOnClass(name = "com.aaron.application.SpringSpi.ConditionalOnTest.StuInfo123")
@Bean
public Object buildObj() {
System.out.println("bean B 被创建、实例化");
return new Integer(7);
}
}

测试结果如下所示,符合预期

figure 3.png

自定义条件注解

事实上,我们还可以通过@Conditional注解来自定义条件注解。具体地,只需给@Conditional注解的value属性设置自定义规则的某个类即可。对于该自定义类而言,需实现Condition接口的matches方法来自定义规则。当该方法返回true为符合期望条件;返回false为不符合期望条件

这里我们想自定义一个条件注解:通过配置项的值是否位于指定区间,来判定是否符合期望条件。我们可以定义一个组合注解@ConditionalOnPropertyRange。其一方面拥有自定义的name、min、max属性;另一方面,在该注解上添加了@Conditional注解,同时指定相应的规则类为OnPropertyRangeCondition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.aaron.application.SpringSpi.CustomConditionalTest;

import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnPropertyRangeCondition.class)
public @interface ConditionalOnPropertyRange {
String name() default "";

int min() default -1;

int max() default -1;
}

这里相应的规则类OnPropertyRangeCondition实现如下所示

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
package com.aaron.application.SpringSpi.CustomConditionalTest;

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

public class OnPropertyRangeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取 ConditionalOnPropertyRange 注解上的参数值
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnPropertyRange.class.getName());
if (annotationAttributes == null || annotationAttributes.isEmpty()) {
return false;
}

String propertyName = (String)annotationAttributes.get("name");
int min = (int) annotationAttributes.get("min");
int max = (int) annotationAttributes.get("max");
// 获取指定配置项的值
Integer propertyValue = context.getEnvironment().getProperty(propertyName, Integer.class);

if( propertyValue==null || propertyValue<min || propertyValue>max ) {
// 条件不通过
return false;
}

// 条件通过
return true;
}
}

现在我们来提供两个配置类,验证我们自定义的条件注解@ConditionalOnPropertyRange

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
package com.aaron.application.SpringSpi.CustomConditionalTest;

import org.springframework.context.annotation.Configuration;

// 配置项aaron.config.weight的值在[min,max]范围区间内,故该配置类会被实例化
@ConditionalOnPropertyRange(name="aaron.config.weight", min=50, max=60)
@Configuration
public class MyConfig8 {
public MyConfig8() {
System.out.println("<My Config 8>: 被实例化");
}
}

...

package com.aaron.application.SpringSpi.CustomConditionalTest;

import org.springframework.context.annotation.Configuration;

// 配置项aaron.config.weight的值不在[min,max]范围区间内,故该配置类不会被实例化
@ConditionalOnPropertyRange(name="aaron.config.weight", min=250, max=260)
@Configuration
public class MyConfig9 {
public MyConfig9() {
System.out.println("<My Config 9>: 被实例化");
}
}

同时,在配置文件application.properties中添加下述配置

1
2
3
...
aaron.config.weight=55
...

测试结果如下所示,符合预期

figure 4.png

请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝