天天看點

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

開心一刻

  那天知道她結婚了,我整整一個晚上沒睡覺,開了三百公裡的車來到她家樓下,緩緩的抽了一支煙......

  天漸漸涼了,響起了鞭炮聲,迎親車隊到了,那天披着婚紗的她很美,真的很美!

  我跟着迎親車隊開了幾公裡的時候,收到了她的資訊:别送了,别送了,你的手扶拖拉機太響了 ......

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

前情回顧

  樓主一而再,再而三的折騰循環依賴,你們不煩,樓主自己都煩了,如果你們實在是受不了,那就...

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  言歸正傳,雖然确實有點像懶婆娘的裹腳布,又臭又長,但确實還是有點東西的,隻要大家堅持看完,肯定會有收獲的!

  我們先回顧下前三探

  一探

  Spring 的循環依賴,源碼詳細分析 → 真的非要三級緩存嗎 中講到了循環依賴問題

   Spring 通過三級緩存解決 setter 循環依賴

  一級緩存 singletonObjects 存的是對外暴露的對象,也就是我們應用真正用到的對象

  二級緩存 earlySingletonObjects 存的是半成品對象或半成品對象的代理對象,用于處理循環依賴的對象建立問題

  三級緩存 singletonFactories 存的是建立對象的工廠方法,用于處理存在 AOP + 循環依賴的對象建立問題

  着重分析了是否一定需要三級緩存來解決循環依賴問題

  二探

   Spring 不能處理構造方法的循環依賴,也不能處理原型循環依賴

  再探循環依賴 → Spring 是如何判定原型循環依賴和構造方法循環依賴的,從源碼的角度分析了 Spring 是如何鑒别構造方法循環依賴、原型循環依賴的

   Set<String> singletonsCurrentlyInCreation 會記錄目前正在建立中的執行個體名稱, Spring 建立執行個體對象之前,會判斷 singletonsCurrentlyInCreation 中是否存在該執行個體的名稱,如果存在則表示産生構造方法循環依賴了

   ThreadLocal<Object> prototypesCurrentlyInCreation 會記錄目前線程正在建立中的原型執行個體名稱, Spring 建立原型執行個體對象之前,會判斷 prototypesCurrentlyInCreation 中是否存在該執行個體的名稱,如果存在則表示産生原型循環依賴了

  三探

  三探循環依賴 → 記一次線上偶現的循環依賴問題,從源碼的角度分析了這次偶現問題可能出現的原因

   BeanDefinition 的掃描順序:以啟動類為起點,掃描啟動類同級目錄下的所有檔案夾,按檔案夾名升序順序進行掃描,會遞歸掃描每個檔案夾,檔案掃描也是按檔案名升序順序進行

   BeanDefinition 覆寫, @Configuration + @Bean 修飾的 BeanDefinition 會覆寫 @Component 修飾的 BeanDefinition , BeanDefinition 的覆寫并不影響 BeanDefinition 的掃描

   Bean 的執行個體化順序,理論上來講,先被掃描到的就先被執行個體化,但執行個體化過程中的屬性填充會打亂這個順序,會将被依賴的對象提前執行個體化

  一通分析下來,雖說沒能找到問題的真正原因,但至少知道了如何去規避這個問題,如何正确的書寫規範的代碼

問題複現

  經過前面三探,樓主以為對 Spring 的循環依賴已經拿捏的死死的了,然而當他出現後,樓主才發現,不是她離不開我,而是我離不開她了

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  我們來看看循環依賴和 BeanPostProcessor 是如何産生愛情的火花的

   SpringBoot 版本 2.0.3.RELEASE ,示例代碼位址:spring-circular-beanpostprocessor

  我們隻需要關注三個類

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!
四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!
四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  依賴很簡單, ServiceAImpl 依賴 ServiceBImpl , ServiceBImpl 也依賴 ServiceAImpl ,這種循環依賴,樓主自認為拿捏的死死的

  直到 BeanPostProcessor 的出現,循環依賴決定不再遷就,她倆的愛情就産生了

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  她倆的愛情資訊:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'serviceAImpl': Bean with name 'serviceAImpl' has been injected into other beans [serviceBImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.      

  此刻,樓主才明白,小醜竟是我自己!

問題分析

  其實她倆的愛情資訊已經提示的很明顯了,樓主再忍痛翻譯一下: serviceAImpl 作為循環依賴的一部分注入到了 serviceBImpl 後,又被包裝了,這就意味着 serviceBImpl 引用的不是最終版本的 serviceAImpl 

  關于 BeanPostProcessor ,樓主不想過多介紹,大家可以檢視:Spring拓展接口之BeanPostProcessor,我們來看看它的底層實作

  從錯誤堆棧資訊,我們可以追蹤到 Spring 報錯的代碼

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

   因為 ServiceAImpl 比 ServiceBImpl 先被掃描,是以 serviceAImpl 先被執行個體化,執行個體化過程如下

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  此時一切都正常,問題就出在 serviceAImpl 填充屬性serviceBImpl 完成之後,我們來 debug 下

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  從 debug 結果可以看到, ServiceBImpl 的執行個體對象 ServiceBImpl@5171 中注入的 ServiceAImpl 對象是 ServiceAImpl@5017 

  而經過 initializeBean(beanName, exposedObject, mbd); 後, Spring 暴露出來的 ServiceAImpl 的最終對象是 $Proxy53@5212 

  這就導緻 ServiceBImpl@5171 中注入的 ServiceAImpl@5017 并不是最終版本的 ServiceAImpl ,她們的愛情就這麼産生了

問題處理

  面對這樣的問題,我們可以怎麼處理了

  @Lazy

  通過 @Lazy 延遲注入,在真正使用到的時候才進行注入

  在任意一個屬性上加 @Lazy 即可,例如

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  或者

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  或者兩個都加上 @Lazy 

  SmartInstantiationAwareBeanPostProcessor

  棄用 BeanPostProcessor ,改用 SmartInstantiationAwareBeanPostProcessor 

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  重寫的方法是: getEarlyBeanReference ,而非 postProcessAfterInitialization 方法,提前暴露代理對象

四探循環依賴 → 當循環依賴遇上 BeanPostProcessor,愛情可能就産生了!

  也就是說在 ServiceAImpl 對象填充屬性(populateBean(beanName, mbd, instanceWrapper))之前,就将代理對象提前暴露到第三級緩存中

  後續給 ServiceBImpl 對象填充 serviceAImpl 屬性時,就用第三級緩存中的 ServiceAImpl 代理對象

  剔除循環依賴

  循環依賴本就不合理,項目中應盡量避免

  至于如何剔除,無法一概而論,需要大家自己去琢磨了

總結

  循環依賴

  雖說 Spring 通過三級緩存解決了 setter 方式的循環依賴,但這不能成為我們有恃無恐的理由

  循環依賴本就不合理,盡量去規避

  真實項目問題

  相信很多小夥伴會有這樣的疑問:樓主,你是怎麼就讓 循環依賴 遇上 BeanPostProcessor ?

  因為已有代碼的不規範,導緻很多地方都産生了循環依賴,而最近又引入 Shareding-JDBC 做分庫,而 Shareding-JDBC 又通過 BeanPostProcessor 來生成代理對象

  就這樣,她倆就相遇了