SpringBoot中@ConditionalOnBean實(shí)現(xiàn)原理解讀
SpringBoot @ConditionalOnBean實(shí)現(xiàn)原理
在SpringBoot1.5.X時(shí)
判斷條件是OR,SpringBoot2.x變?yōu)榱薃ND
在spring 啟動(dòng)過(guò)程中,會(huì)調(diào)用BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry后置處理器 調(diào)用ComponentScanAnnotationParser的parse(..)方法解析配置類,實(shí)現(xiàn)類路徑下的類文件掃描,
遍歷被掃描到的類,并通過(guò)@ComponentScan注解上指定的規(guī)則進(jìn)行匹配,默認(rèn)情況下,SpringBoot掃描@Component標(biāo)記的類,( 但是在spring.factories中通過(guò)key org.springframework.boot.autoconfigure.EnableAutoConfiguration指定配置不會(huì)被掃描. 這些配置類會(huì)由@EnableAutoConfiguration導(dǎo)入的AutoConfigurationImportSelector來(lái)處理);
如果出現(xiàn)匹配的類還會(huì)通過(guò)ConditionEvaluator.shouldSkip(...)來(lái)判斷是否忽略這個(gè)類,這時(shí)候會(huì)過(guò)濾掉小部分無(wú)效類,但是不會(huì)在這個(gè)期間應(yīng)用@ConditionalOnBean去驗(yàn)證,
因?yàn)槠潋?yàn)證實(shí)現(xiàn)類OnBeanCondition實(shí)現(xiàn)了接口ConfigurationCondition的getConfigurationPhase()方法,該方法返回值是一個(gè)枚舉值,用于判斷什么時(shí)期應(yīng)用這個(gè)條件驗(yàn)證, shouldSkip()方法會(huì)判斷這個(gè)接口返回值,OnBeanCondition實(shí)現(xiàn)的getConfigurationPhase()返回的是ConfigurationPhase.REGISTER_BEAN,而類掃描期間只會(huì)執(zhí)行沒(méi)有實(shí)現(xiàn)ConfigurationCondition接口,或返回值為null,或?yàn)镃onfigurationPhase.PARSE_CONFIGURATION的驗(yàn)證器.
所以這個(gè)期間不會(huì)應(yīng)用@ConditionalOnBean去驗(yàn)證. 這也可以理解因?yàn)檫@個(gè)時(shí)候有很多類沒(méi)有被遍歷到,現(xiàn)在執(zhí)行OnBeanCondition驗(yàn)證肯定多數(shù)是false.
這也導(dǎo)致很多最終不會(huì)被實(shí)例化為Spring bean的類被包裝成BeanDefinition(實(shí)現(xiàn)類ScannedGenericBeanDefinition ),并注冊(cè)在DefaultListableBeanFactory 的成員beanDefinitionMap中,key為Bean的名稱,同時(shí)Bean的名稱會(huì)保存在List集合beanDefinitionNames中, 對(duì)于一些手動(dòng)注冊(cè)的Bean的名稱會(huì)保存在List集合manualSingletonNames中,
然后對(duì)于parse(..)返回的BeanDefinition集合會(huì)遍歷調(diào)用ConfigurationClassParser的parse(...)遞歸調(diào)用, 當(dāng)解析到配置中有@Bean定義的Bean,并有條件注解時(shí),會(huì)調(diào)用ConditionEvaluator.shouldSkip(...)方法,最終調(diào)用到的是 Condition實(shí)現(xiàn)類的matches(...)方法.
而@ConditionalOnBean注解中的驗(yàn)證實(shí)現(xiàn)類OnBeanCondition,最終判斷是否有指定的bean就是通過(guò)DefaultListableBeanFactory 中的beanDefinitionMap和manualSingletonNames是否包含指定的Bean的名稱來(lái)驗(yàn)證的.
當(dāng)然對(duì)于掃描到的無(wú)效類,在ConfigurationClassPostProcessor的processConfigBeanDefinitions(...)調(diào)用parse()方法后會(huì)調(diào)用this.reader.loadBeanDefinitions(configClasses),來(lái)判斷最終有效的BeanDefinition,這時(shí)候就會(huì)執(zhí)行OnBeanCondition, 移除beanDefinitionMap中無(wú)效的BeanDefinition.
鑒于類路徑掃描規(guī)則和OnBeanCondition驗(yàn)證實(shí)現(xiàn)原理
就可能導(dǎo)致無(wú)效問(wèn)題
例如如下代碼:
C希望不存在A Bean時(shí)創(chuàng)建,B希望在C bean存在時(shí)創(chuàng)建,示例中A配置了Bean,這時(shí)B C都不應(yīng)該創(chuàng)建,但執(zhí)行結(jié)果是C確實(shí)沒(méi)有創(chuàng)建,而B(niǎo)卻被創(chuàng)建了.
原因就在于類掃描時(shí),C被掃描了,導(dǎo)致beanDefinitionMap存在C 的BeanDefinition.
但是C在this.reader.loadBeanDefinitions(configClasses),中由于條件驗(yàn)證不通過(guò)沒(méi)有創(chuàng)建被移除.(當(dāng)然和類掃描順序也有關(guān)系,如果B能夠在C之后被掃描到,就會(huì)執(zhí)行正常,所以Spring官網(wǎng)建議通過(guò)spring.factories來(lái)配置,指定執(zhí)行順序或者在能夠確定執(zhí)行順序的情況下使用@ConditionalOnBean,但是一定不能通過(guò)文件名來(lái)控制順序)
注意點(diǎn):
如果將示例中的類配置到spring.factories中即時(shí)不指定順序,也能正常執(zhí)行,不會(huì)創(chuàng)建B C,這是由于spring.factories配置文件中的類并不是全部包裝成BeanDefinition然后判斷是否有效的,而是排序后封裝成ConfigurationClass通過(guò)遍歷的方式,同樣是在this.reader.loadBeanDefinitions(configClasses)方法中判斷該類是否通過(guò)條件驗(yàn)證是否有效.
當(dāng)執(zhí)行到B是,C還沒(méi)有被處理,所以導(dǎo)致B也不會(huì)被創(chuàng)建,(這有點(diǎn)屬于巧合導(dǎo)致的正確結(jié)果,所以為了安全還是要執(zhí)行順序,讓B在C之后被處理)
第二種情況, 在配置類中定義Bean,如果使用@ConditionalOnBean依賴的也是配置類中Bean,則執(zhí)行結(jié)果不可控,和配置類加載順序有關(guān)。
示例代碼如下:代碼中是希望Bean3被創(chuàng)建,但是實(shí)際上并沒(méi)有!!!
解決有兩種方式:
1 可以考慮使用@DependsOn來(lái)解決,參看Spring中@DependsOn注解的作用及實(shí)現(xiàn)原理。
2 如果一定要區(qū)分兩個(gè)配置類的先后順序,可以將這兩個(gè)類交與EnableAutoConfiguration管理和觸發(fā)。 也就是定義在META-INF\spring.factories中聲明是配置類, 然后通過(guò)@AutoConfigureBefore、AutoConfigureAfter、AutoConfigureOrder控制先后順序。 因?yàn)檫@三個(gè)注解只對(duì)自動(dòng)配置類生效。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中常用解析工具jackson及fastjson的使用
今天給大家?guī)?lái)的是關(guān)于Java解析工具的相關(guān)知識(shí),文章圍繞著jackson及fastjson的使用展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06SpringMVC中的SimpleUrlHandlerMapping用法詳解
這篇文章主要介紹了SpringMVC中的SimpleUrlHandlerMapping用法詳解,SimpleUrlHandlerMapping是Spring MVC中適用性最強(qiáng)的Handler Mapping類,允許明確指定URL模式和Handler的映射關(guān)系,有兩種方式聲明SimpleUrlHandlerMapping,需要的朋友可以參考下2023-10-10nacos服務(wù)注冊(cè)服務(wù)發(fā)現(xiàn)依賴配置詳解
這篇文章主要為大家介紹了nacos服務(wù)注冊(cè)服務(wù)發(fā)現(xiàn)依賴配置詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Java執(zhí)行cmd命令兩種實(shí)現(xiàn)方法解析
這篇文章主要介紹了Java執(zhí)行cmd命令兩種實(shí)現(xiàn)方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07JavaGUI實(shí)現(xiàn)隨機(jī)單詞答題游戲
這篇文章主要為大家詳細(xì)介紹了JavaGUI實(shí)現(xiàn)隨機(jī)單詞答題游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12Java數(shù)據(jù)結(jié)構(gòu)之循環(huán)隊(duì)列簡(jiǎn)單定義與用法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之循環(huán)隊(duì)列簡(jiǎn)單定義與用法,簡(jiǎn)要描述了循環(huán)隊(duì)列的概念、原理,并結(jié)合實(shí)例形式分析了java循環(huán)隊(duì)列的定義與使用方法,需要的朋友可以參考下2017-10-10