? ? ? ?SpringBoot條件注解@Conditional溶诞,可用于根據(jù)某個(gè)特定的條件來(lái)判斷是否需要?jiǎng)?chuàng)建某個(gè)特定的Bean涤浇。SpringBoot自動(dòng)配置功能里面就大量的使用了條件注解饱亿。接下來(lái)我們就對(duì)@Conditional的使用做一個(gè)簡(jiǎn)單的介紹。
? ? ? ?@Conditional注解需要和Condition接口搭配一起使用覆致。通過(guò)對(duì)應(yīng)Condition接口來(lái)告知是否滿足匹配條件利耍。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 所有用于匹配的Condition接口(實(shí)現(xiàn)該接口的類)肠骆,只有這些類都返回true才認(rèn)為是滿足條件
*/
Class<? extends Condition>[] value();
}
? ? ? ?@Conditional注解可以添加在@Configuration算途、@Component、@Service等修飾的類上用于控制對(duì)應(yīng)的Bean是否需要?jiǎng)?chuàng)建蚀腿,或者添加在@Bean修飾的方法上用于控制方法對(duì)應(yīng)的Bean是否需要?jiǎng)?chuàng)建嘴瓤。
@Conditional添加在@Configuration修飾的類上扫外,用于控制該類和該類里面所有添加的@Bean方法對(duì)應(yīng)的Bean是否需要?jiǎng)?chuàng)建。
一 @Conditional擴(kuò)展注解
? ? ? ?為了方便我們的使用Spring Boot對(duì)@Conditional條件注解做了一些擴(kuò)展廓脆,提供了一些很實(shí)用的擴(kuò)展性條件注解筛谚。
條件注解 | Condition處理類 | 實(shí)例 | 解釋 |
---|---|---|---|
@ConditionalOnBean | OnBeanCondition | @ConditionalOnBean(DataSource.class) | Spring容器中不存在對(duì)應(yīng)的實(shí)例生效 |
@ConditionalOnMissingBean | OnBeanCondition | @ConditionalOnMissingBean(name = "redisTemplate") | Spring容器中不存在對(duì)應(yīng)的實(shí)例生效 |
@ConditionalOnSingleCandidate | OnBeanCondition | @ConditionalOnSingleCandidate(FilteringNotifier.class) | Spring容器中是否存在且只存在一個(gè)對(duì)應(yīng)的實(shí)例,或者雖然有多個(gè)但 是指定首選的Bean生效 |
@ConditionalOnClass | OnClassCondition | @ConditionalOnClass(RedisOperations.class) | 類加載器中存在對(duì)應(yīng)的類生效 |
@ConditionalOnMissingClass | OnClassCondition | @ConditionalOnMissingClass(RedisOperations.class) | 類加載器中不存在對(duì)應(yīng)的類生效 |
@ConditionalOnExpression | OnExpressionCondition | @ConditionalOnExpression(“’${server.host}’==’localhost’”) | 判斷SpEL 表達(dá)式成立生效 |
@ConditionalOnJava | OnJavaCondition | @ConditionalOnJava(JavaVersion.EIGHT) | 指定Java版本符合要求生效 |
@ConditionalOnProperty | OnPropertyCondition | @ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true) | 應(yīng)用環(huán)境中的屬性滿足條件生效 |
@ConditionalOnResource | OnResourceCondition | @ConditionalOnResource(resources=”mybatis.xml”) | 存在指定的資源文件生效 |
@ConditionalOnWebApplication | OnWebApplicationCondition | 當(dāng)前應(yīng)用是Web應(yīng)用生效 | |
@ConditionalOnNotWebApplication | OnWebApplicationCondition | 當(dāng)前應(yīng)用不是Web應(yīng)用生效 |
? ? ? ?上面的擴(kuò)展注解我們可以簡(jiǎn)單的分為以下幾類:
- Bean作為條件:@ConditionalOnBean停忿、@ConditionalOnMissingBean驾讲、@ConditionalOnSingleCandidate。
- 類作為條件:@ConditionalOnClass席赂、@ConditionalOnMissingClass吮铭。
- SpEL表達(dá)式作為條件:@ConditionalOnExpression。
- JAVA版本作為條件: @ConditionalOnJava
- 配置屬性作為條件:@ConditionalOnProperty颅停。
- 資源文件作為條件:@ConditionalOnResource谓晌。
- 是否Web應(yīng)用作為判斷條件:@ConditionalOnWebApplication、@ConditionalOnNotWebApplication癞揉。
1.1 Bean作為條件
1.1.1 @ConditionalOnBean
? ? ? ?@ConditionalOnBean對(duì)應(yīng)的Condition處理類是OnBeanCondition纸肉。如果Spring容器里面存在指定的Bean則生效。
@ConditionalOnBean配置參數(shù)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
/**
* 需要作為條件的類的Class對(duì)象數(shù)組
*/
Class<?>[] value() default {};
/**
* 需要作為條件的類的Name, Class.getName()
*/
String[] type() default {};
/**
* (用于指定注解修飾的Bean)條件所需的注解類
*/
Class<? extends Annotation>[] annotation() default {};
/**
* Spring容器中Bean的名字
*/
String[] name() default {};
/**
* 搜索容器層級(jí)喊熟,當(dāng)前容器毁靶,父容器
*/
SearchStrategy search() default SearchStrategy.ALL;
/**
* 可能在其泛型參數(shù)中包含指定Bean類型的其他類
*/
Class<?>[] parameterizedContainer() default {};
}
1.1.2 @ConditionalOnMissingBean
? ? ? ?@ConditionalOnMissingBean對(duì)應(yīng)的Condition實(shí)現(xiàn)類是OnBeanCondition。如果Spring容器里面不存在指定的Bean則生效逊移。
@ConditionalOnMissingBean配置參數(shù)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
/**
* 需要作為條件的類的Class對(duì)象數(shù)組
*/
Class<?>[] value() default {};
/**
* 需要作為條件的類的Name, Class.getName()
*/
String[] type() default {};
/**
* 匹配Bean的時(shí)候需要忽視的Class對(duì)象數(shù)組,一般是父類
* @ConditionalOnMissingBean(value = JdbcFactory.class, ignored = MySqlDefaultFactory.class)
*/
Class<?>[] ignored() default {};
/**
* 匹配Bean的時(shí)候需要忽視的類的Name, Class.getName()
*/
String[] ignoredType() default {};
/**
* (用于指定注解修飾的Bean)條件所需的注解類
*/
Class<? extends Annotation>[] annotation() default {};
/**
* Spring容器中Bean的名字
*/
String[] name() default {};
/**
* 搜索容器層級(jí)龙填,當(dāng)前容器胳泉,父容器
*/
SearchStrategy search() default SearchStrategy.ALL;
/**
* 可能在其泛型參數(shù)中包含指定Bean類型的其他類
*/
Class<?>[] parameterizedContainer() default {};
}
? ? ? ?比如如下的實(shí)例,當(dāng)容器里面不存在redisTemplate對(duì)應(yīng)的Bean的時(shí)候岩遗,就會(huì)創(chuàng)建一個(gè)RedisTemplate添加到容器里面去扇商。
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
1.1.3 @ConditionalOnSingleCandidate
? ? ? ?@ConditionalOnSingleCandidate對(duì)應(yīng)的Condition處理類是OnBeanCondition。如果當(dāng)指定Bean在容器中只有一個(gè)宿礁,或者雖然有多個(gè)但是指定首選Bean的時(shí)候則生效案铺。
@ConditionalOnSingleCandidate配置參數(shù)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnSingleCandidate {
/**
* 需要作為條件的類的Class對(duì)象
*/
Class<?> value() default Object.class;
/**
* 需要作為條件的類的Name, Class.getName()
*/
String type() default "";
/**
* 搜索容器層級(jí),當(dāng)前容器梆靖,父容器
*/
SearchStrategy search() default SearchStrategy.ALL;
}
1.2 類作為條件
1.2.1 @ConditionalOnClass
? ? ? ?@ConditionalOnClass對(duì)應(yīng)的Condition處理類是OnClassCondition控汉。如果當(dāng)前類路徑下面有指定的類的時(shí)候則生效。
@ConditionalOnClass配置屬性介紹
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
* 需要作為條件的類的Class對(duì)象數(shù)組
*/
Class<?>[] value() default {};
/**
* 需要作為條件的類的Name, Class.getName()
*/
String[] name() default {};
}
1.2.2 @ConditionalOnMissingClass
? ? ? ?@ConditionalOnMissingClass對(duì)應(yīng)的Condition處理類是OnClassCondition返吻。如果當(dāng)前類路徑下面沒(méi)有指定的類的時(shí)候則生效姑子。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnMissingClass {
/**
* 需要作為條件的類的Name, Class.getName()
*/
String[] value() default {};
}
1.3 SpEL表達(dá)式作為條件
? ? ? ?@ConditionalOnExpression對(duì)應(yīng)的Condition處理類是OnExpressionCondition。只有當(dāng)SpEL表達(dá)式滿足條件的時(shí)候則生效测僵。
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {
/**
* 要作為條件的SpEL表達(dá)式
*/
String value() default "true";
}
? ? ? ?例如@ConditionalOnExpression("${test.enabled:true}")街佑,只有當(dāng)配置文件里面存在test.enabled: true的時(shí)候則生效。
更加詳細(xì)的用法可以去看下SpEL表達(dá)式的使用。
1.4 JAVA版本作為判斷條件
? ? ? ?@ConditionalOnJava對(duì)應(yīng)的Condition處理類是OnJavaCondition沐旨。只有當(dāng)指定的JAVA版本條件滿足的時(shí)候森逮,才會(huì)創(chuàng)建對(duì)應(yīng)的Bean。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnJavaCondition.class)
public @interface ConditionalOnJava {
/**
* 比較方式磁携,Range.EQUAL_OR_NEWER:當(dāng)前版本等于或高于褒侧、Range.OLDER_THAN:當(dāng)前版本老于,越早的版本越老
*/
Range range() default Range.EQUAL_OR_NEWER;
/**
* 指定JAVA版本
*/
JavaVersion value();
/**
* Range options.
*/
enum Range {
/**
* Equal to, or newer than the specified {@link JavaVersion}.
*/
EQUAL_OR_NEWER,
/**
* Older than the specified {@link JavaVersion}.
*/
OLDER_THAN
}
}
1.5 配置屬性作為判斷條件
? ? ? ?@ConditionalOnProperty對(duì)應(yīng)的Condition實(shí)現(xiàn)類OnPropertyCondition颜武。只有當(dāng)對(duì)應(yīng)的配置屬性和給定條件的值相等的時(shí)候則生效璃搜。
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
/**
* 對(duì)應(yīng)property名稱的值
*/
String[] value() default {};
String[] name() default {};
/**
* property名稱的前綴,可有可無(wú)
*/
String prefix() default "";
/**
* 與name組合使用鳞上,比較獲取到的屬性值與havingValue給定的值是否相同这吻,相同才加載配置
*/
String havingValue() default "";
/**
* 缺少該property時(shí)是否可以加載。如果為true篙议,沒(méi)有該property也會(huì)正常加載唾糯;反之報(bào)錯(cuò)
*/
boolean matchIfMissing() default false;
}
? ? ? ?@ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”)表示當(dāng)配置文件里面spring.aop.auto=true的時(shí)候才會(huì)加載對(duì)應(yīng)的Bean。
1.6 資源文件是否存在作為判斷條件
? ? ? ?@ConditionalOnResource對(duì)應(yīng)的Condition處理類OnResourceCondition鬼贱。只有當(dāng)指定的資源文件出現(xiàn)在classpath中則生效移怯。
@ConditionalOnResource配置屬性
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnResourceCondition.class)
public @interface ConditionalOnResource {
/**
* 要作為判斷條件的資源文件名稱 @ConditionalOnResource(resources=”mybatis.xml”)
*/
String[] resources() default {};
}
1.7 是否Web應(yīng)用作為判斷條件
1.7.1 @ConditionalOnWebApplication
? ? ? ?@ConditionalOnWebApplication對(duì)應(yīng)的Condition處理類是OnWebApplicationCondition。只有當(dāng)當(dāng)前項(xiàng)目是Web項(xiàng)目的時(shí)候則生效这难。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
/**
* 需要作為條件的Web應(yīng)用程序的必需類型
*/
Type type() default Type.ANY;
/**
* Available application types.
*/
enum Type {
/**
* 任何web應(yīng)用都將匹配
*/
ANY,
/**
* 僅基于servlet的Web應(yīng)用程序?qū)⑵ヅ? */
SERVLET,
/**
* 僅基于反應(yīng)式的Web應(yīng)用程序?qū)⑵ヅ? */
REACTIVE
}
}
1.7.2 @ConditionalOnNotWebApplication
? ? ? ?@ConditionalOnNotWebApplication對(duì)應(yīng)的Condition處理類是OnWebApplicationCondition舟误。只有當(dāng)當(dāng)前項(xiàng)目不是Web項(xiàng)目的時(shí)候則生效。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnNotWebApplication {
}
二 @Conditional自定義
? ? ? ?上面介紹每個(gè)擴(kuò)展注解的時(shí)候都特意提到了每個(gè)注解對(duì)應(yīng)的Condition實(shí)現(xiàn)類姻乓。其實(shí)我們可以仿照這些Condition實(shí)現(xiàn)類來(lái)實(shí)現(xiàn)我們自己的@Conditional注解嵌溢。下面我們同個(gè)兩個(gè)簡(jiǎn)單的實(shí)例來(lái)看下怎么實(shí)現(xiàn)自己的@Conditional擴(kuò)展注解。
2.1 判斷是否配置指定屬性
? ? ? ?注意蹋岩,和@ConditionalOnProperty不一樣哦赖草,@ConditionalOnProperty是判斷是否有屬性并且判斷值是否等于我們指定的值。我們要實(shí)現(xiàn)的注解只判斷有沒(méi)有配置屬性剪个,不管屬性對(duì)應(yīng)的值秧骑。
? ? ? ?擴(kuò)展注解ConditionalOnPropertyExist。指定我們的Condition實(shí)現(xiàn)類OnPropertyExistCondition扣囊。并且指定兩個(gè)參數(shù)乎折。一個(gè)是參數(shù)name用于指定屬性。另一個(gè)參數(shù)exist用于指定是判斷存在還是不存在如暖。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(OnPropertyExistCondition.class)
public @interface ConditionalOnPropertyExist {
/**
* 配置文件里面對(duì)應(yīng)的key
*/
String name() default "";
/**
* 是否有配置的時(shí)候判斷通過(guò)
*/
boolean exist() default true;
}
? ? ? ?OnPropertyExistCondition類就是簡(jiǎn)單的判斷下屬性存在與否笆檀。
public class OnPropertyExistCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Map<String, Object> annotationAttributes = annotatedTypeMetadata.getAnnotationAttributes(ConditionalOnPropertyExist.class.getName());
if (annotationAttributes == null) {
return false;
}
String propertyName = (String) annotationAttributes.get("name");
boolean values = (boolean) annotationAttributes.get("exist");
String propertyValue = conditionContext.getEnvironment().getProperty(propertyName);
if(values) {
return !StringUtils.isEmpty(propertyValue);
} else {
return StringUtils.isEmpty(propertyValue);
}
}
}
2.1 判斷是否配置指定屬性
? ? ? ?我們簡(jiǎn)單實(shí)現(xiàn)這樣一個(gè)功能,根據(jù)指定的系統(tǒng)加載不同的Bean盒至。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(OnSystemCondition.class)
public @interface ConditionalOnSystem {
/**
* 指定系統(tǒng)
*/
SystemType type() default SystemType.WINDOWS;
/**
* 系統(tǒng)類型
*/
enum SystemType {
/**
* windows系統(tǒng)
*/
WINDOWS,
/**
* linux系統(tǒng)
*/
LINUX,
/**
* mac系統(tǒng)
*/
MAC
}
}
public class OnSystemCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionalOnSystem.class.getName());
if (annotationAttributes == null) {
return false;
}
ConditionalOnSystem.SystemType systemType = (ConditionalOnSystem.SystemType) annotationAttributes.get("type");
switch (systemType) {
case WINDOWS:
return context.getEnvironment().getProperty("os.name").contains("Windows");
case LINUX:
return context.getEnvironment().getProperty("os.name").contains("Linux ");
case MAC:
return context.getEnvironment().getProperty("os.name").contains("Mac ");
}
return false;
}
}