SpringBoot 自動(dòng)配置失效的解決方法
本文源自近期項(xiàng)目中遇到的問題, bug 總是出現(xiàn)在你自以為是的地方...
問題描述
下面是一個(gè)簡(jiǎn)單復(fù)現(xiàn)的代碼片段,在你沒有閱讀完本文時(shí),如果能做出正確的判斷,那恭喜你可以節(jié)省閱讀本文的時(shí)間了。
1、自動(dòng)配置類:AutoTestConfiguration
@Configuration @EnableConfigurationProperties(TestProperties.class) @ConditionalOnProperty(prefix = "test", name = "enable") public class AutoTestConfiguration { @Bean @ConditionalOnMissingBean public TestBean testBean(TestProperties properties){ System.out.println("this is executed....."); return new TestBean(); } }
2、配置類 TestProperties
@ConfigurationProperties(prefix = "test") public class TestProperties { private boolean enable = true; public boolean isEnable() { return enable; } public void setEnable(boolean enable) { this.enable = enable; } }
這兩個(gè)類都在 root package 下,可以保證能夠正常被 Spring 掃描到;那么問題是 TestBean 會(huì)不會(huì)被正常創(chuàng)建?當(dāng)然這里的結(jié)論是不會(huì)。
可能有的同學(xué)會(huì)說你的 TestProperties 沒有加 @Configuration 注解,Spring 不認(rèn)識(shí)它,那真的是這樣嗎?很顯然也不是。
在排查這個(gè)問題的過程中,也有遇到其他問題,也是之前沒有遇到過的;即使 Spring 源碼我看過很多遍,但是仍然會(huì)有一些邊邊角角讓你意想不到的地方;下面就針對(duì)這個(gè)問題,慢慢來揭開它的面紗。
@EnableConfigurationProperties 注解行為
在之前的版本中,TestProperties 是有被 @Configuration 注解標(biāo)注的
@Configuration // 可以被 spring 掃描 @ConfigurationProperties(prefix = "test") public class TestProperties { private boolean enable = true; public boolean isEnable() { return enable; } public void setEnable(boolean enable) { this.enable = enable; } }
常規(guī)的思路是,當(dāng) TestProperties 被掃描到之后,spring env 中就會(huì)有 test.enable=true 的 k-v 存在,當(dāng)執(zhí)行 AutoTestConfiguration 自動(dòng)配置類刷新時(shí),@ConditionalOnProperty(prefix = "test", name = "enable") 則會(huì)生效,進(jìn)而 TestBean 被正常創(chuàng)建。
但事實(shí)并非如此,下面是對(duì)于此問題的驗(yàn)證
配置有效,AutoTestConfiguration 未刷新
兩個(gè)點(diǎn):
- AutoTestConfiguration#testBean 執(zhí)行會(huì)輸出一個(gè) log(用于判斷 AutoTestConfiguration 是否正常刷新)
- 監(jiān)聽 ApplicationReadyEvent 事件,拿 test.enable 值(用于判端配置是否正常加載,也就是 TestProperties 是否被正常刷新)
代碼如下:
@SpringBootApplication public class Application implements ApplicationListener<ApplicationReadyEvent> { @Autowired private ApplicationContext applicationContext; public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Override public void onApplicationEvent(ApplicationReadyEvent event) { System.out.println(this.applicationContext.getEnvironment().getProperty("test.enable") + "------"); } }
執(zhí)行得到的結(jié)果是 AutoTestConfiguration#testBean 沒有被執(zhí)行,但test.enable 為 true。
這里說明 TestProperties 是有被刷新的,但是并沒有對(duì) @ConditionalOnProperty 起到作用,那么這里基本可以猜到是自動(dòng)配置類上的 @ConditionalOnProperty 和 @EnableConfigurationProperties 的作用順序問題。
在驗(yàn)證順序問題之前,我嘗試在 application.properties 中增加如下配置,re run 項(xiàng)目:
test.enable=true
到這里我得到了另一個(gè) bean 沖突的問題。
prefix-type
異常提示如下:
Parameter 0 of method testBean in com.glmapper.bridge.boot.config.AutoTestConfiguration required a single bean, but 2 were found:
- testProperties: defined in file [/Users/glmapper/Documents/project/exception-guides/target/classes/com/glmapper/bridge/boot/config/TestProperties.class]
- test-com.glmapper.bridge.boot.config.TestProperties: defined in null
這里出現(xiàn)了 test-com.glmapper.bridge.boot.config.TestProperties 這個(gè) name 的 bean。我嘗試在代碼中去檢查是否有顯示給定這個(gè) bean 名字,但是沒有找到,那只有一種可能,就是這個(gè)是被 spring 自己創(chuàng)建的。
這個(gè)過程在 spring 刷新階段非常靠前,在排查這個(gè)問題時(shí),還是耽誤了一些時(shí)間,最后還是把問題定位一致前置到 beandefinitions 初始化才找到。
這里是 @EnableConfigurationProperties 注解的一個(gè)行為,依賴 EnableConfigurationPropertiesRegistrar,源碼如下:
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar { .getQualifiedAttributeName(EnableConfigurationPropertiesRegistrar.class, "methodValidationExcludeFilter"); @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerInfrastructureBeans(registry); registerMethodValidationExcludeFilter(registry); ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry); // to register getTypes(metadata).forEach(beanRegistrar::register); }
通過代碼比較容易看出,EnableConfigurationPropertiesRegistrar 會(huì)將目標(biāo) metadata 注冊(cè)成 bean;繼續(xù) debug,找到了產(chǎn)生 prefix-type 格式 name 的 bean。
下面是 getName 的具體代碼
private String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) { // 拿 prefix String prefix = annotation.isPresent() ? annotation.getString("prefix") : ""; // prefix + "-" + 類全限定名 return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName()); }
到這里我們先明確一個(gè)問題:
如果你使用 @EnableConfigurationProperties 來開啟配置類,那么就不要在配置類上使用@Configuration 等能夠被 Spring scan 識(shí)別到的注解,以免在后續(xù)的使用中同一類型的 bean 多個(gè)實(shí)例
@ConditionalOnProperty
在回到配置不生效問題上來,這里在官方 issue 是有記錄的:github.com/spring-proj…
不過這里還是通過分析代碼來還原下問題產(chǎn)生的根本原因;這里主要從兩個(gè)方面來分析:
- @ConditionalOnProperty match 值邏輯,需要明確在匹配 value 時(shí),從哪些 PropertySource 讀取的。
- @ConditionalOnProperty match 失敗和 bean 刷新的邏輯
@ConditionalOnProperty match 邏輯
首先是 @ConditionalOnProperty 在執(zhí)行計(jì)算時(shí),匹配 value 的值來源問題,通過 debug 代碼很容易就得到了所有的 source 來源,如下圖:
從 debug 看,本案例有 4 個(gè)來源(具體如上圖),實(shí)際上從源碼來看,source 涵蓋了 spring env 所有來源:
[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, StubPropertySource {name='servletConfigInitParams'}, StubPropertySource {name='servletContextInitParams'}, PropertiesPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}, OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}]
所以本文案例中不生效原因就是上面這些 PropertySource 都沒有 test.enable,也就是 TestProperties 沒被刷新,或者其在自動(dòng)配置類之后才刷新。
@ConditionalOnProperty skip 邏輯
這里主要解釋 @ConditionalOnPropert 和 bean 被刷新的邏輯關(guān)系,具體實(shí)現(xiàn)在 ConditionEvaluator 類中
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { // 1、沒有 Conditional 注解,則掃描時(shí)不會(huì)跳過當(dāng)前 bean // 2、遍歷 conditions 進(jìn)行判斷是否滿足 }
所以對(duì)于自動(dòng)配置類上的注解,Conditional 是作為當(dāng)前類是否允許被刷新的前提,只有 Conditional 條件滿足,才會(huì)將當(dāng)前的自動(dòng)配置類加入到待刷新 bean 列表中去,如果 Conditional 不滿足,這個(gè) bean 將直接被跳過,不會(huì)被放到 BeandefinitonMap 中去,也就不會(huì)有后續(xù)的刷新動(dòng)作。
@ConditionalOnProperty 作用時(shí)機(jī)在 BeanDefiniton 被創(chuàng)建之前,其執(zhí)行時(shí)機(jī)要比 @EnableConfigurationProperties 作用要早,這也就說明了,為什么 TestProperties 中 test.enable=true, AutoTestConfiguration 也不會(huì)刷新的原因了。
總結(jié)
本文通過一個(gè)簡(jiǎn)單 case,對(duì)于項(xiàng)目中遇到的 SpringBoot 配置失效導(dǎo)致 bean 未被刷新問題進(jìn)行了回溯,總結(jié)如下:
Conditional 相關(guān)注解對(duì)于自動(dòng)配置類來說,作用時(shí)機(jī)較早,用于決定當(dāng)前自動(dòng)配置類是否允許被刷新
@EnableConfigurationProperties enable 的類,會(huì)默認(rèn)注冊(cè)一個(gè) bean,bean 名字格式為 prefix-type
到此這篇關(guān)于SpringBoot 自動(dòng)配置失效的解決方法的文章就介紹到這了,更多相關(guān)SpringBoot 自動(dòng)配置失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java SpringBoot自動(dòng)配置原理詳情
- Springboot @Configuration與自動(dòng)配置詳解
- Springboot自動(dòng)配置與@Configuration配置類詳解
- SpringBoot快速通關(guān)自動(dòng)配置應(yīng)用
- SpringBoot超詳細(xì)講解自動(dòng)配置原理
- SpringBoot自動(dòng)配置實(shí)現(xiàn)的詳細(xì)步驟
- SpringBoot2入門自動(dòng)配置原理及源碼分析
- SpringBoot自動(dòng)配置原理詳解
- SpringBoot自動(dòng)配置深入探究實(shí)現(xiàn)原理
相關(guān)文章
SpringBoot實(shí)現(xiàn)統(tǒng)一封裝返回前端結(jié)果集的示例代碼
在實(shí)際項(xiàng)目開發(fā)過程中,我們經(jīng)常將返回?cái)?shù)據(jù)的基本形式統(tǒng)一為JSON格式的數(shù)據(jù)。但項(xiàng)目可能是由很多人開發(fā)的,所以我們最好將返回的結(jié)果統(tǒng)一起來。本文介紹了SpringBoot實(shí)現(xiàn)統(tǒng)一封裝返回前端結(jié)果集的示例代碼,需要的可以參考一下2022-06-06SpringBoot中實(shí)現(xiàn)啟動(dòng)任務(wù)的實(shí)現(xiàn)步驟
這篇文章主要介紹了SpringBoot中實(shí)現(xiàn)啟動(dòng)任務(wù)的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09spring cloud gateway全局過濾器實(shí)現(xiàn)向request header中放數(shù)據(jù)
這篇文章主要介紹了spring cloud gateway全局過濾器實(shí)現(xiàn)向request header中放數(shù)據(jù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07SpringBoot 關(guān)于Feign的超時(shí)時(shí)間配置操作
這篇文章主要介紹了SpringBoot 關(guān)于Feign的超時(shí)時(shí)間配置操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09在Spring Data JPA中引入Querydsl的實(shí)現(xiàn)方式
這篇文章主要介紹了在Spring Data JPA中引入Querydsl的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01java中 利用正則表達(dá)式提取( )內(nèi)內(nèi)容
本篇文章,小編為大家介紹關(guān)于java中 利用正則表達(dá)式提取( )內(nèi)內(nèi)容,有需要的朋友可以參考一下2013-04-04