點選「 IT碼徒 」, 關註,置頂 公眾號
每日技術幹貨,第一時間送達!
疑慮背景
疑慮描述
最近,在進行開發的過程中,發現之前的一個寫法,類似如下
以我的理解,
@Configuration
加
@Bean
會建立一個 userName 不為 null 的 UserManager 物件,而
@Component
也會建立一個 userName 為 null 的 UserManager 物件
那麽我們在其他物件中註入 UserManager 物件時,到底註入的是哪個物件?
因為計畫已經上線了很長一段時間了,所以這種寫法沒有編譯報錯,執行也沒有出問題
後面去找同事了解下,實際是想讓
生效,而實際也確實是它生效了
那麽問題來了: Spring 容器中到底有幾個 UserManager 型別的物件?
Spring Boot 版本
計畫中用的 Spring Boot 版本是:
2.0.3.RELEASE
物件的 scope 是預設值,也就是 singleton
結果驗證
驗證方式有很多,可以 debug 跟源碼,看看 Spring 容器中到底有幾個 UserManager 物件,也可以直接從 UserManager 構造方法下手,看看哪幾個構造方法被呼叫,等等
我們從構造方法下手,看看 UserManager 到底例項化了幾次
只有有參構造方法被呼叫了,無參構造方法巋然不動(根本沒被呼叫)
既然 UserManager 構造方法只被呼叫了一次,那麽前面的問題:到底註入的是哪個物件
答案也就清晰了,沒得選了呀,只能是
@Configuration
加
@Bean
建立的 userName 不為 null 的 UserManager 物件
問題又來了:
為什麽不是
@Component
建立的 userName 為 null 的 UserManager 物件?
源碼解析
@Configuration
與
@Component
關系很緊密
所以
@Configuration
能夠被
component scan
其中
Configuration classPostProcessor
與
@Configuration
息息相關,其類繼承結構圖如下:
它實作了
BeanFactoryPostProcessor
介面和
PriorityOrdered
介面,關於
BeanFactoryPostProcessor
,
那麽我們從
AbstractApplicationContext
的 refresh 方法呼叫的
invokeBeanFactoryPostProcessors(beanFactory)
開始,來跟下源碼
此時完成了
com.lee.qsl
包下的
component scan
,
com.lee.qsl
包及子包下的 UserConfig 、 UserController 和 UserManager 都被掃描出來
註意,此刻
@Bean
的處理還未開始, UserManager 是透過
@Component
而被掃描出來的;此時 Spring 容器中
beanDefinitionMap
中的 UserManager 是這樣的
接下來一步很重要,與我們想要的答案息息相關
迴圈遞迴處理 UserConfig 、 UserController 和 UserManager ,把它們都封裝成
Configuration class
,遞迴掃描
BeanDefinition
迴圈完之後,我們來看看
config classes
UserConfig bean
定義資訊中 beanMethods 中有一個元素 [
BeanMethod:name=userManager,declaring class=com.lee.qsl.config.UserConfig
]
然後我們接著往下走,來仔細看看答案出現的環節
是不是有什麽發現?
@Component
修飾的 UserManager 定義直接被覆蓋成了
@Configuration + @Bean
修飾的 UserManager 定義
Bean 定義型別也由
ScannedGenericBeanDefinition
替換成了
Configuration classBeanDefinition
後續透過
BeanDefinition
建立例項的時候,建立的自然就是
@Configuration + @Bean
修飾的 UserManager ,也就是會反射呼叫 UserManager 的有參構造方法
自此,答案也就清楚了
Spring 其實給出了提示
2021-10-03 20:37:33.697 INFO 13600 --- [
main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'userManager' with a different definition: replacing [Generic bean: class [com.lee.qsl.manager.UserManager]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\qsl-project\spring-boot-bean-component\target\ classes\com\lee\qsl\manager\UserManager. class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=userConfig; factoryMethodName=userManager; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/lee/qsl/config/UserConfig. class]]
只是日誌級別是 info ,太不顯眼了
Spring 升級最佳化
可能 Spring 團隊意識到了 info 級別太不顯眼的問題,或者說意識到了直接覆蓋的處理方式不太合理
所以在
Spring 5.1.2.RELEASE
(Spring Boot 則是 2.1.0.RELEASE )做出了最佳化處理
我們來具體看看
啟動直接報錯,Spring 也給出了提示
The bean 'userManager', defined in class path resource [com/lee/qsl/config/UserConfig. class], could not be registered. A bean with that name has already been defined in file [D:\qsl-project\spring-boot-bean-component\target\ classes\com\lee\qsl\manager\UserManager. class] and overriding is disabled.
我們來跟下源碼,主要看看與
Spring 5.0.7.RELEASE
的區別
新增了配置項
allowBeanDefinitionOverriding
來控制是否允許
BeanDefinition
覆蓋,預設情況下是不允許的
我們可以在配置檔中配置:
spring.main.allow-bean-definition-overriding=true
,允許
BeanDefinition
覆蓋
這種處理方式是更優的,將選擇權交給開發人員,而不是自己偷偷的處理,已達到開發者想要的效果
總結
Spring 5.0.7.RELEASE
(
Spring Boot 2.0.3.RELEASE
) 支持
@Configuration + @Bean
與
@Component
同時作用於同一個類
啟動時會給 info 級別的日誌提示,同時會將
@Configuration + @Bean
修飾的 BeanDefinition 覆蓋掉
@Component
修飾的 BeanDefinition
也許 Spring 團隊意識到了上述處理不太合適,於是在
Spring 5.1.2.RELEASE
做出了最佳化處理
增加了配置項:
allowBeanDefinitionOverriding
,將主動權交給了開發者,由開發者自己決定是否允許覆蓋
補充
關於
allowBeanDefinitionOverriding
,前面講的不對,後面特意去翻了下源碼,補充如下
Spring 1.2 引進
DefaultListableBeanFactory
的時候就有了
private boolean allowBeanDefinitionOverriding = true;
,預設是允許
BeanDefinition
覆蓋
Spring 4.1.2 引進了
isAllowBeanDefinitionOverriding()
方法
Spring 自始至終預設都是允許
BeanDefinition
覆蓋的,變的是 Spring Boot , Spring Boot 2.1.0 之前沒有覆蓋 Spring 的
allowBeanDefinitionOverriding
預設值,仍是允許
BeanDefinition
覆蓋的
Spring Boot 2.1.0 中 SpringApplication 定義了私有內容:
allowBeanDefinitionOverriding
沒有顯示的指定值,那麽預設值就是 false ,之後在 Spring Boot 啟動過程中,會用此值覆蓋掉 Spring 中的
allowBeanDefinitionOverriding
的預設值
關於
allowBeanDefinitionOverriding
,我想大家應該已經清楚了
來源:cnblogs.com/youzhibing/p/15354706.html
— END —
PS:防止找不到本篇文章,可以收藏點贊,方便翻閱尋找哦。
往期推薦