Spring解決循環依賴相關問題分析

Spring解決循環依賴的關鍵spring

  1. 三級緩存
  2. 提早曝光

什麼是三級緩存

三級緩存的代碼以下:
image.png緩存

  • 第一級緩存 singletonObjects(就是咱們一般所說的單例池)
  • 第二級緩存 earlySingletonObjects
  • 第三級緩存 singletonFactories(存儲的是建立bean的工廠,工廠在源碼中就是lambda表達式,它持有一個原始對象,要是須要發現這個bean須要被代理,執行這個lambda則會返回這個bean的代理對象)

bean中定義了切面、有@Transactional或@Async這類的加強註解,則必須在元素對象的基礎上加上增長邏輯,生成代理類異步

**注意只有一級緩存用了ConcurrentHashMap, 第二級、第三級都是使用了普通的hashmap,由於除了一級緩存,查找、新增和刪除二三級緩存節點的關鍵代碼使用同步代碼塊實現
image-20210330212735999.png
事實上若是隻使用ConcurrentHashMap,不使用同步代碼塊反而會有問題,由於ConcurrentHashMap只能保證對節點單個操做的原子性、可見性,好比想上面先判斷沒有再新增就無法完成,get方法也不是一個絕對正確的結果。函數

bean一個大概的建立流程:
  1. 找到須要管理的bean,根據class生成BeanDefinition
  2. new出一個原始對象
  3. 填充依賴的屬性
  4. 調用BeanPostProcessor
  5. 把這個bean添加到單例池(一級緩存)

思考:只使用兩級緩存如何解決循環依賴

那麼AService和BService完成初始化的過程是什麼樣的呢?post

  1. ASerive生成BeanDefinition,new出一個原始對象,把這個原始對象傳遞給構造Lambda持有,把這個Lambda放到三級緩存中
  2. AService開始填充屬性,發現須要填充BService,從一三級緩存依次查找,發現都沒有BServie,調用BService的建立
  3. BSerive生成BeanDefinition,new出一個原始對象,把這個原始對象傳遞給構造Lambda持有,把這個Lambda放到三級緩存中
  4. 開始屬性填充,發現須要依賴ASerive,一級緩存中找不到,因而從三級緩存中找生成bean的工廠,發現能找到,因而調用這個工廠Lambda表達式,返回AService並填充到屬性中,BService完成屬性注入,走完建立流程,添加到單例池中
  5. Bservice的建立函數返回,AService接着進行屬性填充,從單例池中找到BService放到本身屬性中,ASerive就能夠完成建立了

這樣看好像只須要第一級緩存和第三級緩存就能夠解決循環依賴的問題了spa

Spring爲何使用到了三級緩存緩存呢?

其實使用三級緩存主要是在須要被代理的bean出現循環依賴的時候起做用.net

須要被代理時只使用一級和三級緩存存在的問題

咱們先思考一下若是AService上定義了切面,這時候三級緩存中的構造Lambda返回的就不是原始對象了,而是被代理後的對象,這時候上面的流程中第4步中調用Lambda返回的就是AService的一個代理對象
假設AService一樣依賴CService,而CService也須要依賴AService,這樣AService填充進BService後,會接着填充CService, 使得CService也開始進行建立,CService的建立時須要注入AService,一樣能從三級緩存中找到工廠,調用工廠一樣返回AService的一個代理對象,這樣BService和CService建立過程都生成了一個AService的代理對象,並且持有的都不是同一個AService!代理

如何利用第二級緩存解決代理bean的循環依賴

其實也比較簡單,上面的步驟中第4步BService從三級緩存中獲取到工廠生產代理對象時,把這個對象放到第二級緩存中,這樣CService須要注入AService時會一二三級緩存依次判斷,最後能在二級緩存中直接獲得代理對象,就不會去三級緩存中獲取工廠構建一個了code

關於代理bean執行BeanPostProcessor的細節

其實生成代理對象通常仍是在執行BeanPostProcessor的這一步執行的,在這一步會根據原始對象生成代理對象放到單例池中
只用出現了循環依賴的時候纔會調用三級緩存中的工廠提早進行代理,上面的流程中第四步,BService填充AService時發現BSerice不在單例池中時候,就能夠評定必定是出現循環依賴了,接着在二級緩存中也找不到,因而開始找三級緩存中的工廠進行提早代理
可是要是以後BeanPostProcessor有生成一個代理對象放到單例池中,不是又出現了兩個代理對象了嗎?對象

BeanPostProcessor如何判斷已經提早進行了代理,再也不生成代理對象

spring是在執行提早代理加強時把原始對象又放到了一個專門用來作判斷是否已經進行了aop的map(earlyProxyReferences)裏面,lambda加強代碼以下:
image-20210331220423404.png
在後置處理器執行aop的方法,先去這個map裏面去找這個對象,要是發現這個map裏面有這個bean了就再也不執行aop了
image-20210331220607331.png

@Async註解引起的Spring循環依賴分析

@Aysnc 註解的Bean的建立代理的時機特別特殊:
@EnableAsync開啓時會向容器注入AsyncAnnotationBeanPostProcessor,它是一個BeanPostProcessor,實現了postProcessAfterInitialization,具體的實如今其父類AbstractAdvisingBeanPostProcessor中,在設個PostProcessor會生成一個代理,就及完成了提早代理也會生成一個新的代理

一個類有同時有循環依賴提早代理 @Async生成代理 的狀況時,會生成兩個不一樣的代理對象,一個是異步的代理對象,就會致使代碼在AbstractAutowireCapableBeanFactory.doCreateBean方法中直接拋出錯誤(最後一步會判斷二級緩存中的對象和Processor執行後的對象不一致時拋出異常)

解決辦法:

  1. 或者在依賴注入的地方,加上@lazy註解
    @Autowired 注入時配合@lazy是怎麼起做用的跟着源碼看了一下,核心意思就是在Spring初始化ioc容器時,使用@Lazy的屬性能夠不加載,這樣啓動不報錯,運行時再加載)
  2. 將用@Async註解 的方法都移出去放到另外一個類中
相關文章
相關標籤/搜索