SpringBoot用配置影響B(tài)ean加載@ConditionalOnProperty
故事背景
故事發(fā)生在幾個(gè)星期前,自動(dòng)化平臺(tái)代碼開(kāi)放給整個(gè)測(cè)試團(tuán)隊(duì)以后,越來(lái)越多的同事開(kāi)始探索平臺(tái)代碼。為了保障自動(dòng)化測(cè)試相關(guān)的數(shù)據(jù)和沉淀能不被污染,把數(shù)據(jù)庫(kù)進(jìn)行了隔離。終于有了2個(gè)數(shù)據(jù)庫(kù)實(shí)例,一個(gè)給dev環(huán)境用,一個(gè)給test環(huán)境用。可是隨著平臺(tái)的發(fā)展,越來(lái)越多的中間件被引用了。所以需要隔離的東西就越來(lái)越多了,比如MQ,Redis等。成本越來(lái)越高,如果像數(shù)據(jù)庫(kù)實(shí)例一樣全部分開(kāi)搞一套,那在當(dāng)下全域降本增效的大潮下,也是困難重重。
?
通過(guò)線下觀察和走訪發(fā)現(xiàn),這些探索的同學(xué)并不是需要全平臺(tái)的能力,其中有很多模塊或者子系統(tǒng),同學(xué)并不關(guān)心。因此就產(chǎn)生了一個(gè)想法,隔離掉這些類(lèi)或者不使用這些和中間件相關(guān)的類(lèi)應(yīng)該就可以了 。而后因?yàn)槲覀兊钠脚_(tái)是基于springboot開(kāi)發(fā)的,自然而然的想到了@Conditional注解。
調(diào)試&解決
以AWS SQS為例,先添加上了注解@ConditionalOnProperty根據(jù)配置信息中的coverage.aws.topic屬性進(jìn)行判斷,如果存在這個(gè)配置就進(jìn)行CoverageSQSConfig的Spring Bean的加載。
@Configuration
@ConditionalOnProperty(
name = "coverage.aws.topic"
)
public class CoverageSQSConfig {
@Value("${coverage.aws.region}")
private String awsRegion;
@Value("${coverage.aws.access.key}")
private String accessKey;
@Value("${coverage.aws.secret.key}")
private String secretKey;
@Bean(name = "coverageSQSListenerFactory")
public DefaultJmsListenerContainerFactory sqsListenerContainerFactory(){
return getDefaultJmsListenerContainerFactory(awsRegion, accessKey, secretKey);
}
private DefaultJmsListenerContainerFactory getDefaultJmsListenerContainerFactory(String awsRegion, String accessKey, String secretKey) {
DefaultJmsListenerContainerFactory sqsFactory = new DefaultJmsListenerContainerFactory();
sqsFactory.setConnectionFactory(new SQSConnectionFactory(
new ProviderConfiguration(),
AmazonSQSClientBuilder.standard()
.withRegion(Region.of(awsRegion).id())
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
.build()));
sqsFactory.setConcurrency("3-10");
sqsFactory.setReceiveTimeout(10*1000L);
sqsFactory.setRecoveryInterval(1000L);
return sqsFactory;
}
}
為調(diào)試這個(gè)內(nèi)容的效果,這里列出了2次調(diào)試的效果對(duì)比:首先是把備注字段全部都注釋掉。


通過(guò)上圖很明顯,當(dāng)coverage.aws.topic屬性不存在的時(shí)候,不能找到被Spring統(tǒng)一管理的bean。
第二次是把備注的注釋都取消掉,重啟后能找到bean。

問(wèn)題解決了嗎?當(dāng)時(shí)就想再看下SpringBoot是怎么做的通過(guò)這個(gè)注解就這么方便的過(guò)濾了這個(gè)bean的加載,以及是否有什么其他的用法或者特性。
SpringBoot 是怎么做的
通過(guò)@ConditionalOnProperty注解,很快能定位到它是位于 autoconfigure模塊的特性。**
**

順藤摸瓜,很快就能找到注解是在哪里進(jìn)行使用的
package org.springframework.boot.autoconfigure.condition;
...
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 通過(guò)獲類(lèi)原始數(shù)據(jù)上的ConditionalOnProperty注解的參數(shù)值
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
// 通過(guò)屬性值,逐一判斷配置信息中的信息是否滿(mǎn)足 , context.getEnvironment() 能獲取到所有的配置信息
ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}
private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(
MultiValueMap<String, Object> multiValueMap) {
...
return annotationAttributes;
}
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
Spec spec = new Spec(annotationAttributes);
List<String> missingProperties = new ArrayList<>();
List<String> nonMatchingProperties = new ArrayList<>();
// 通過(guò)屬性值,判斷配置信息中的信息是否滿(mǎn)足
spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
if (!missingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
}
if (!nonMatchingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.found("different value in property", "different value in properties")
.items(Style.QUOTE, nonMatchingProperties));
}
return ConditionOutcome
.match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
}
private static class Spec {
private final String prefix;
private final String havingValue;
private final String[] names;
private final boolean matchIfMissing;
Spec(AnnotationAttributes annotationAttributes) {
...
}
private String[] getNames(Map<String, Object> annotationAttributes) {
...
}
private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
for (String name : this.names) {
String key = this.prefix + name;
if (resolver.containsProperty(key)) {
// havingValue 默認(rèn)為 ""
if (!isMatch(resolver.getProperty(key), this.havingValue)) {
nonMatching.add(name);
}
}
else {
if (!this.matchIfMissing) {
missing.add(name);
}
}
}
}
private boolean isMatch(String value, String requiredValue) {
if (StringUtils.hasLength(requiredValue)) {
return requiredValue.equalsIgnoreCase(value);
}
// havingValue 默認(rèn)為 "" ,因此只要對(duì)應(yīng)的屬性不為false,在注解中沒(méi)填havingValue的情況下,都是會(huì)match上conditon,即都會(huì)被加載
return !"false".equalsIgnoreCase(value);
}
@Override
public String toString() {
...
}
}
}
用這種方式進(jìn)行SpingBoot擴(kuò)展的也特別多,SpingBoot自己的autoconfigure模塊中有很多模塊的增強(qiáng)用的也是這個(gè)注解。

那他是在哪個(gè)環(huán)節(jié)進(jìn)行的這個(gè)condition的判斷呢?簡(jiǎn)單標(biāo)注如下:

其中判斷過(guò)濾的總?cè)肟冢?/p>
// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
/**
* Determine whether the given class does not match any exclude filter
* and does match at least one include filter.
* @param metadataReader the ASM ClassReader for the class
* @return whether the class qualifies as a candidate component
*/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
// conditons 相關(guān)的入口,
return isConditionMatch(metadataReader);
}
}
return false;
}
環(huán)顧整個(gè)流程,這里比較好的一點(diǎn)就是一旦條件過(guò)濾后,那就對(duì)類(lèi)元文件里面的其他內(nèi)容也不進(jìn)行加載,像下面的@Value和@Bean的填充也不會(huì)進(jìn)行,能優(yōu)雅高效的解決掉當(dāng)前的問(wèn)題。
@Value("${coverage.aws.region}")
private String awsRegion;
@Value("${coverage.aws.access.key}")
private String accessKey;
@Value("${coverage.aws.secret.key}")
private String secretKey;
@Bean(name = "coverageSQSListenerFactory")
public DefaultJmsListenerContainerFactory sqsListenerContainerFactory(){
return getDefaultJmsListenerContainerFactory(awsRegion, accessKey, secretKey);
}
故事的最后
做完這個(gè)改動(dòng)以后,就提交了代碼,媽媽再也不用擔(dān)心因?yàn)槠渌瞬恍⌒氖褂媚承┲挥幸粋€(gè)實(shí)例的中間件導(dǎo)致數(shù)據(jù)污染了。用注解方式解決這個(gè)通過(guò)配置就能控制加載bean的這個(gè)能力確實(shí)很方便很Boot。比如中間件團(tuán)隊(duì)提供組件能力給團(tuán)隊(duì),用condtion的這個(gè)特性也是能方便落地的。當(dāng)然condition里面還有其他的一些特性,這里只是拋磚引玉,簡(jiǎn)單的梳理一下最近的一個(gè)使用場(chǎng)景。
以上就是SpringBoot用配置影響B(tài)ean加載@ConditionalOnProperty的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Bean加載@ConditionalOnProperty的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring注解驅(qū)動(dòng)之ApplicationListener用法解讀
這篇文章主要介紹了Spring注解驅(qū)動(dòng)之ApplicationListener用法解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
SpringBoot+MyBatisPlus對(duì)Map中Date格式轉(zhuǎn)換處理的方法詳解
在?SpringBoot?項(xiàng)目中,?如何統(tǒng)一?JSON?格式化中的日期格式。本文將為大家介紹一種方法:利用MyBatisPlus實(shí)現(xiàn)對(duì)Map中Date格式轉(zhuǎn)換處理,需要的可以參考一下2022-10-10
SpringBoot如何進(jìn)行對(duì)象復(fù)制的實(shí)踐
本文主要介紹了SpringBoot 如何進(jìn)行對(duì)象復(fù)制,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Springboot AOP對(duì)指定敏感字段數(shù)據(jù)加密存儲(chǔ)的實(shí)現(xiàn)
本篇文章主要介紹了利用Springboot+AOP對(duì)指定的敏感數(shù)據(jù)進(jìn)行加密存儲(chǔ)以及對(duì)數(shù)據(jù)中加密的數(shù)據(jù)的解密的方法,代碼詳細(xì),具有一定的價(jià)值,感興趣的小伙伴可以了解一下2021-11-11
Java使用泛型Class實(shí)現(xiàn)消除模板代碼
Class作為實(shí)現(xiàn)反射功能的類(lèi),在開(kāi)發(fā)中經(jīng)常會(huì)用到,然而,當(dāng)Class遇上泛型后,事情就變得不是那么簡(jiǎn)單了,所以本文就來(lái)講講Java如何使用泛型Class實(shí)現(xiàn)消除模板代碼,需要的可以參考一下2023-06-06
JSP服務(wù)器端和前端出現(xiàn)亂碼問(wèn)題解決方案
這篇文章主要介紹了JSP服務(wù)器端和前端出現(xiàn)亂碼問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
springboot結(jié)合redis實(shí)現(xiàn)搜索欄熱搜功能及文字過(guò)濾
本文主要介紹了springboot結(jié)合redis實(shí)現(xiàn)搜索欄熱搜功能及文字過(guò)濾,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02

