SpringBoot用配置影響B(tài)ean加載@ConditionalOnProperty
故事背景
故事發(fā)生在幾個星期前,自動化平臺代碼開放給整個測試團隊以后,越來越多的同事開始探索平臺代碼。為了保障自動化測試相關的數(shù)據(jù)和沉淀能不被污染,把數(shù)據(jù)庫進行了隔離。終于有了2個數(shù)據(jù)庫實例,一個給dev環(huán)境用,一個給test環(huán)境用??墒请S著平臺的發(fā)展,越來越多的中間件被引用了。所以需要隔離的東西就越來越多了,比如MQ,Redis等。成本越來越高,如果像數(shù)據(jù)庫實例一樣全部分開搞一套,那在當下全域降本增效的大潮下,也是困難重重。
?
通過線下觀察和走訪發(fā)現(xiàn),這些探索的同學并不是需要全平臺的能力,其中有很多模塊或者子系統(tǒng),同學并不關心。因此就產(chǎn)生了一個想法,隔離掉這些類或者不使用這些和中間件相關的類應該就可以了 。而后因為我們的平臺是基于springboot開發(fā)的,自然而然的想到了@Conditional注解。
調(diào)試&解決
以AWS SQS為例,先添加上了注解@ConditionalOnProperty根據(jù)配置信息中的coverage.aws.topic屬性進行判斷,如果存在這個配置就進行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)試這個內(nèi)容的效果,這里列出了2次調(diào)試的效果對比:首先是把備注字段全部都注釋掉。
通過上圖很明顯,當coverage.aws.topic屬性不存在的時候,不能找到被Spring統(tǒng)一管理的bean。
第二次是把備注的注釋都取消掉,重啟后能找到bean。
問題解決了嗎?當時就想再看下SpringBoot是怎么做的通過這個注解就這么方便的過濾了這個bean的加載,以及是否有什么其他的用法或者特性。
SpringBoot 是怎么做的
通過@ConditionalOnProperty注解,很快能定位到它是位于 autoconfigure模塊的特性。**
**
順藤摸瓜,很快就能找到注解是在哪里進行使用的
package org.springframework.boot.autoconfigure.condition; ... @Order(Ordered.HIGHEST_PRECEDENCE + 40) class OnPropertyCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { // 通過獲類原始數(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) { // 通過屬性值,逐一判斷配置信息中的信息是否滿足 , 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<>(); // 通過屬性值,判斷配置信息中的信息是否滿足 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 默認為 "" 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 默認為 "" ,因此只要對應的屬性不為false,在注解中沒填havingValue的情況下,都是會match上conditon,即都會被加載 return !"false".equalsIgnoreCase(value); } @Override public String toString() { ... } } }
用這種方式進行SpingBoot擴展的也特別多,SpingBoot自己的autoconfigure模塊中有很多模塊的增強用的也是這個注解。
那他是在哪個環(huán)節(jié)進行的這個condition的判斷呢?簡單標注如下:
其中判斷過濾的總?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 相關的入口, return isConditionMatch(metadataReader); } } return false; }
環(huán)顧整個流程,這里比較好的一點就是一旦條件過濾后,那就對類元文件里面的其他內(nèi)容也不進行加載,像下面的@Value和@Bean的填充也不會進行,能優(yōu)雅高效的解決掉當前的問題。
@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); }
故事的最后
做完這個改動以后,就提交了代碼,媽媽再也不用擔心因為其他人不小心使用某些只有一個實例的中間件導致數(shù)據(jù)污染了。用注解方式解決這個通過配置就能控制加載bean的這個能力確實很方便很Boot。比如中間件團隊提供組件能力給團隊,用condtion的這個特性也是能方便落地的。當然condition里面還有其他的一些特性,這里只是拋磚引玉,簡單的梳理一下最近的一個使用場景。
以上就是SpringBoot用配置影響B(tài)ean加載@ConditionalOnProperty的詳細內(nèi)容,更多關于SpringBoot Bean加載@ConditionalOnProperty的資料請關注腳本之家其它相關文章!
相關文章
Spring注解驅(qū)動之ApplicationListener用法解讀
這篇文章主要介紹了Spring注解驅(qū)動之ApplicationListener用法解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09SpringBoot+MyBatisPlus對Map中Date格式轉(zhuǎn)換處理的方法詳解
在?SpringBoot?項目中,?如何統(tǒng)一?JSON?格式化中的日期格式。本文將為大家介紹一種方法:利用MyBatisPlus實現(xiàn)對Map中Date格式轉(zhuǎn)換處理,需要的可以參考一下2022-10-10Springboot AOP對指定敏感字段數(shù)據(jù)加密存儲的實現(xiàn)
本篇文章主要介紹了利用Springboot+AOP對指定的敏感數(shù)據(jù)進行加密存儲以及對數(shù)據(jù)中加密的數(shù)據(jù)的解密的方法,代碼詳細,具有一定的價值,感興趣的小伙伴可以了解一下2021-11-11springboot結(jié)合redis實現(xiàn)搜索欄熱搜功能及文字過濾
本文主要介紹了springboot結(jié)合redis實現(xiàn)搜索欄熱搜功能及文字過濾,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02