天天看點

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

點選上方“服務端思維”,選擇“設為星标”

回複”669“擷取獨家整理的精選資料集

回複”加群“加入全國服務端高端社群「後端圈」

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

作者 | 半分、笑

出品 | http://u6.gg/kj00k

在使用spring架構的日常開發中,bean之間的循環依賴太頻繁了,spring已經幫我們去解決循環依賴問題,對我們開發者來說是無感覺的,下面具體分析一下spring是如何解決bean之間循環依賴,為什麼要使用到三級緩存,而不是二級緩存?

bean生命周期

首先大家需要了解一下bean在spring中的生命周期,bean在spring的加載流程,才能夠更加清晰知道spring是如何解決循環依賴的

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

我們在spring的BeanFactory工廠列舉了很多接口,代表着bean的生命周期,我們主要記住的是我圈紅線圈出來的接口, 再結合spring的源碼來看這些接口主要是在哪裡調用的

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

AbstractAutowireCapableBeanFactory類的doCreateBean方法是建立bean的開始,我們可以看到首先需要執行個體化這個bean,也就是在堆中開辟一塊記憶體空間給這個對象,createBeanInstance方法裡面邏輯大概就是采用反射生成執行個體對象,進行到這裡表示對象還并未進行屬性的填充,也就是@Autowired注解的屬性還未得到注入

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

我們可以看到第二步就是填充bean的成員屬性,populateBean方法裡面的邏輯大緻就是對使用到了注入屬性的注解就會進行注入,如果在注入的過程發現注入的對象還沒生成,則會跑去生産要注入的對象,第三步就是調用initializeBean方法初始化bean,也就是調用我們上述所提到的接口

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

可以看到initializeBean方法中,首先調用的是使用的Aware接口的方法,我們具體看一下invokeAwareMethods方法中會調用Aware接口的那些方法

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

我們可以知道如果我們實作了BeanNameAware,BeanClassLoaderAware,BeanFactoryAware三個Aware接口的話,會依次調用setBeanName(), setBeanClassLoader(), setBeanFactory()方法,再看applyBeanPostProcessorsBeforeInitialization源碼

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

發現會如果有類實作了BeanPostProcessor接口,就會執行postProcessBeforeInitialization方法,這裡需要注意的是:如果多個類實作BeanPostProcessor接口,那麼多個實作類都會執行postProcessBeforeInitialization方法,可以看到是for循環依次執行的,還有一個注意的點就是如果加載A類到spring容器中,A類也重寫了BeanPostProcessor接口的postProcessBeforeInitialization方法,這時要注意A類的postProcessBeforeInitialization方法并不會得到執行,因為A類還未加載完成,還未完全放到spring的singletonObjects一級緩存中。

再看一個注意的點

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?
為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

可以看到ApplicationContextAwareProcessor也實作了BeanPostProcessor接口,重寫了postProcessBeforeInitialization方法,方法裡面并調用了invokeAwareInterfaces方法,而invokeAwareInterfaces方法也寫着如果實作了衆多的Aware接口,則會依次執行相應的方法,值得注意的是ApplicationContextAware接口的setApplicationContext方法,再看一下invokeInitMethods源碼

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

發現如果實作了InitializingBean接口,重寫了afterPropertiesSet方法,則會調用afterPropertiesSet方法,最後還會調用是否指定了init-method,可以通過标簽,或者@Bean注解的initMethod指定,最後再看一張applyBeanPostProcessorsAfterInitialization源碼圖

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

發現跟之前的postProcessBeforeInitialization方法類似,也是循環周遊實作了BeanPostProcessor的接口實作類,執行postProcessAfterInitialization方法。整個bean的生命執行流程就如上面截圖所示,哪個接口的方法在哪裡被調用,方法的執行流程

最後,對bean的生命流程進行一個流程圖的總結

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

三級緩存解決循環依賴

上一小節對bean的生命周期做了一個整體的流程分析,對spring如何去解決循環依賴的很有幫助。前面我們分析到填充屬性時,如果發現屬性還未在spring中生成,則會跑去生成屬性對象執行個體

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

我們可以看到填充屬性的時候,spring會提前将已經執行個體化的bean通過ObjectFactory半成品暴露出去,為什麼稱為半成品是因為這時候的bean對象執行個體化,但是未進行屬性填充,是一個不完整的bean執行個體對象

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

spring利用singletonObjects, earlySingletonObjects, singletonFactories三級緩存去解決的,所說的緩存其實也就是三個Map

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

可以看到三級緩存各自儲存的對象,這裡重點關注二級緩存earlySingletonObjects和三級緩存singletonFactory,一級緩存可以進行忽略。前面我們講過先執行個體化的bean會通過ObjectFactory半成品提前暴露在三級緩存中

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

singletonFactory是傳入的一個匿名内部類,調用ObjectFactory.getObject()最終會調用getEarlyBeanReference方法。再來看看循環依賴中是怎麼拿其它半成品的執行個體對象的。

我們假設現在有這樣的場景AService依賴BService,BService依賴AService

  1. AService首先執行個體化,執行個體化通過ObjectFactory半成品暴露在三級緩存中
  2. 填充屬性BService,發現BService還未進行過加載,就會先去加載BService
  3. 再加載BService的過程中,執行個體化,也通過ObjectFactory半成品暴露在三級緩存
  4. 填充屬性AService的時候,這時候能夠從三級緩存中拿到半成品的ObjectFactory
為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

拿到ObjectFactory對象後,調用ObjectFactory.getObject()方法最終會調用getEarlyBeanReference()方法,getEarlyBeanReference這個方法主要邏輯大概描述下如果bean被AOP切面代理則傳回的是beanProxy對象,如果未被代理則傳回的是原bean執行個體,這時我們會發現能夠拿到bean執行個體(屬性未填充),然後從三級緩存移除,放到二級緩存earlySingletonObjects中,而此時B注入的是一個半成品的執行個體A對象,不過随着B初始化完成後,A會繼續進行後續的初始化操作,最終B會注入的是一個完整的A執行個體,因為在記憶體中它們是同一個對象。下面是重點,我們發現這個二級緩存好像顯得有點多餘,好像可以去掉,隻需要一級和三級緩存也可以做到解決循環依賴的問題???

隻要兩個緩存确實可以做到解決循環依賴的問題,但是有一個前提這個bean沒被AOP進行切面代理,如果這個bean被AOP進行了切面代理,那麼隻使用兩個緩存是無法解決問題,下面來看一下bean被AOP進行了切面代理的場景

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

我們發現AService的testAopProxy被AOP代理了,看看傳入的匿名内部類的getEarlyBeanReference傳回的是什麼對象

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

發現singletonFactory.getObject()傳回的是一個AService的代理對象,還是被CGLIB代理的。再看一張再執行一遍singletonFactory.getObject()傳回的是否是同一個AService的代理對象

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

我們會發現再執行一遍singleFactory.getObject()方法又是一個新的代理對象,這就會有問題了,因為AService是單例的,每次執行singleFactory.getObject()方法又會産生新的代理對象,假設這裡隻有一級和三級緩存的話,我每次從三級緩存中拿到singleFactory對象,執行getObject()方法又會産生新的代理對象,這是不行的,因為AService是單例的,所有這裡我們要借助二級緩存來解決這個問題,将執行了singleFactory.getObject()産生的對象放到二級緩存中去,後面去二級緩存中拿,沒必要再執行一遍singletonFactory.getObject()方法再産生一個新的代理對象,保證始終隻有一個代理對象。還有一個注意的點

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

既然singleFactory.getObject()傳回的是代理對象,那麼注入的也應該是代理對象,我們可以看到注入的确實是經過CGLIB代理的AService對象。是以如果沒有AOP的話确實可以兩級緩存就可以解決循環依賴的問題,如果加上AOP,兩級緩存是無法解決的,不可能每次執行singleFactory.getObject()方法都給我産生一個新的代理對象,是以還要借助另外一個緩存來儲存産生的代理對象

總結

前面先講到bean的加載流程,了解了bean加載流程對spring如何解決循環依賴的問題很有幫助,後面再分析到spring為什麼需要利用到三級緩存解決循環依賴問題,而不是二級緩存。網上可以試試AOP的情形,實踐一下就能明白二級緩存為什麼解決不了AOP代理的場景了

在工作中,一直認為程式設計代碼不是最重要的,重要的是在工作中所養成的程式設計思維。

— 本文結束 —

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

● 漫談設計模式在 Spring 架構中的良好實踐

● 颠覆微服務認知:深入思考微服務的七個主流觀點

● 人人都是 API 設計者

● 一文講透微服務下如何保證事務的一緻性

● 要黑盒測試微服務内部服務間調用,我該如何實作?

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

關注我,回複 「加群」 加入各種主題讨論群。

對「服務端思維」有期待,請在文末點個在看

喜歡這篇文章,歡迎轉發、分享朋友圈

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?

在看點這裡

為什麼Spring需要三級緩存解決循環依賴,而不是二級緩存?