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