日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

作者:郭艷紅

 

以下舉例皆針對單例模式討論

圖解參考 https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce?

1、Spring 如何創建Bean?

對于單例Bean來說,在Spring容器整個生命周期內,有且只有一個對象。

Spring 在創建 Bean 過程中,使用到了三級緩存,即 DefaultSingletonBeanRegistry.JAVA 中定義的:

/** Cache of singleton objects: bean name to bean instance. */

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

 

/** Cache of singleton factories: bean name to ObjectFactory. */

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

 

/** Cache of early singleton objects: bean name to bean instance. */

private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

以 com.gyh.general 包下的 OneBean 為例,debug springboot 啟動過程,分析spring是如何創建bean的。

參考圖中 spring創建bean 的過程。其中最關鍵的幾步有:

1.getSingleton(beanName, true) 依次從一二三級緩存中查找bean對象,如果緩存中存在對象,則直接返回(early);

2.createBeanInstance(beanName, mbd, args) 選一個合適的構造函數,new實例對象(instance),此時的instance中依賴的屬性還都是null,屬于半成品;

3.singletonFactories.put(beanName, oneSingletonFactory) 利用上一步的instance,構建一個 singletonFactory,并將其放到三級緩存中;

4.populateBean(beanName, mbd, instanceWrApper) 填充bean:為該bean定義的屬性創建對象或賦值;

5.initializeBean("one",oneInstance, mbd) 初始化bean:對bean進行初始化或其他加工,如生成代理對象(proxy);

6.getSingleton(beanName, false) 依次在一二級緩存中查找,檢查是否有因循環依賴導致提前生成的對象,有的話與初始化后的對象是否一致;

2、Spring 如何解決循環依賴?

以 com.gyh.circular.threeCache 包下的 OneBean 和 TwoBean 為例 ,兩個 Bean 相互依賴(即形成閉環)。

參考圖中 spring解決循環依賴 的過程可知,spring利用三級緩中的 objectFactory 生成并返回一個 early 對象,提前暴露這個 early 地址,供其他對象依賴注入使用,以此解決循環依賴問題。

3、Spring 不能解決哪些循環依賴?

3.1 循環中使用了 @Async 注解

3.1.1 為什么循環中使用了 @Async 會報錯?

以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 為例,兩個bean相互依賴,且oneBean中的方法使用了 @Async 注解,此時啟動spring失敗,報錯信息為:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a.one': Bean with name 'a.one' has been injected into other beans [a.two] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

并通過debug代碼,發現報錯位置在 AbstractAutowireCapableBeanFactory#doCreateBean 方法內,由于 earlySingletonReference != null 且 exposedObject != bean,導致報錯。

結合流程圖中 spring解決循環依賴 及上述圖片中可知:

1.行1中 bean 為 createBeanInstance 創建的實例(address1)

2.行2中 exposedObject 為 initializeBean 后生成的代理對象(address2)

3.行3中 earlySingletonReference 為 getEarlyBeanReference 時創建的對象【此處地址同bean(address1)】

深層原因為:先前 TwoBean 在 populateBean 時已經依賴了地址為 address1 的 earlySingletonReference 對象,而此時 OneBean 經過 initializeBean 之后,返回了地址為 address2 的新對象,導致spring不知道哪個才是最終版的bean,所以報錯。

earlySingletonReference 是如何生成的,參考getSingleton("one", true)過程。

3.1.2 循環中使用了 @Async 一定會報錯嗎?

依然以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 為例,兩個bean相互依賴,使 TwoBean(非OneBean)中的方法使用了 @Async 注解,此時啟動spring成功,并未報錯。

debug代碼可知:雖然TwoBean 使用了 @Async 注解,但其 earlySingletonReference = null; 故不會引起報錯。

深層原因為:OneBean 先被創建,TwoBean 后創建,再整條鏈路中,并未在三級緩存中查找過 TwoBean 的 objectFactory 。(OneBean在創建過程中,被找過兩次,即 one-> two ->one;TwoBean 的創建過程中,只找過它一次,即 two ->one。)

由此可得:@Async 造成循環依賴報錯的先約條件為:

1.循環依賴中的 Bean 使用了 @Async 注解

2.且這個 Bean,比循環內其他 Bean 先創建。

3.注:一個Bean可能會同時存在于多個循環內;只要存在它是某個循環內第一個被創建的Bean,那么就會報錯。

3.1.3 為什么循環中使用了 @Transactional 不會報錯?

已知使用了 @Transactional 注解的 Bean,Spring 也會為其生成代理對象,但為什么這種 Bean 在循環里時不會產生報錯呢?

以 com.gyh.circular.transactional 包下的 OneBean 和 TwoBean 為例,兩個 Bean 相互依賴,且 OneBean 中的方法使用了 @Transactional 注解,啟動Spring成功,并不會報錯。

debug 代碼可知,生成 OneBean 過程中,雖然 earlySingletonReference != null,但 initializeBean 之后的 exposedObject 和 原始實例的地址相同(即 initializeBean 步驟中,并未對實例生成代理),所以不會產生報錯。

3.1.4 為什么同樣是代理會產生兩種不同的現象?

同樣是生成代理對象,同樣是參與到循環依賴中,會產生不同現象的原因是:當他們處在循環依賴中時,生成代理的節點不同:

1.@Transactional 在 getEarlyBeanReference 時生成代理,提前暴露出代理之后的地址(即最終地址);

2.@Async 在 initializeBean 時生成代理,導致提前暴露出去的地址不是最終地址,造成報錯。

為什么 @Async 不能在 getEarlyBeanReference 時生成代理呢?對比下兩者執行的代碼過程發現:

兩者都是在 AbstractAutoProxyCreator#getEarlyBeanReference 的方法對原始實例對象進行包裝,如下圖

使用 @Transactional 的Bean 在 create proxy 時,獲取到一個advice ,隨即生成了代理對象 proxy.

而使用 @Async 的Bean 在 create proxy 時,沒有獲取到 advice,不能被代理.

3.1.5 為什么@Async 在 getEarlyBeanReference 時不能返回一個 advice?

在 AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean 方法內,其主要做的事情有:

1.找到當前 spring 容器中所有的 Advisor

2.返回適配當前 bean 的所有 Advisor

第一步返回的 Advisor 有 BeanFactoryCacheOperationSourceAdvisor 和 BeanFactoryTransactionAttributeSourceAdvisor,并無處理 Async 相關的 Advisor.

刨根問底,追查為什么第一步不會返回處理 Async 相關的 Advisor?

已知使用 @Async @Transactional @Cacheable 需要提前進行開啟,即提前標注 @EnableAsync、@EnableTransactionManagement、@EnableCaching 。

以 @EnableTransactionManagement、@EnableCaching 為例,在其注解定義中,引入了Selector類,Selector中又引入了Configuration 類,在 Configuration 類中,創建了對應 Advisor 并放到了 spring容器中,所以第一步才能得到這兩個 Advisor.

而 @EnableAsync的定義中引入的 Configuration 類,創建的是 AsyncAnnotationBeanPostProcessor 并非一個 Advisor,所以第一步不會得到它,所以 @Async 的 bean 不會在這一步被代理。

3.2 構造函數引起的循環依賴

以 com.gyh.circular.constructor 包下的 OneBean 和 TwoBean 為例,兩個類的構造函數中各自依賴對方,啟動spring,報錯:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c.one': Requested bean is currently in creation: Is there an unresolvable circular reference?

debug 代碼可知,兩個bean在根據構造函數 new instance 時,就已經陷入的死循環,無法提前暴露可用的地址,所以只能報錯。

4、如何解決以上循環依賴報錯?

1.不用 @Async,將需要異步操作的方法,放到線程池中執行。(推薦)

2.提出 @Async 標注的方法。(推薦)

3.將使用 @Async 的方法提出到單獨的類中,該類只做異步處理,不做其他業務依賴,即避免形成循環依賴,從而解決報錯問題。參考 com.gyh.circular.async.extract 包。

4.盡量不使用構造函數依賴對象。(推薦)

5.破壞循環(不推薦)即不形成閉環,在開發之前,規劃好對象依賴,方法調用鏈,盡量做到不使用循環依賴。(較難,隨著迭代開發不斷變化,很可能產生循環)

6.破壞創建順序(不推薦)

7.由于使用 @Async 注解的所在類,比循環依賴內其他類先創建時才會報錯,那么想辦法使該類不先于其他類先創建,也可解決該問題,如:@DependsOn、 @Lazy

分享到:
標簽:Spring
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定