添加微信BGM7756 可免費領取面試資料複製代碼
Spring中的循環依賴一直是Spring中一個很重要的話題,一方面是由於源碼中爲了解決循環依賴作了不少處理,另一方面是由於面試的時候,若是問到Spring中比較高階的問題,那麼循環依賴一定逃不掉。若是你回答得好,那麼這就是你的必殺技,反正,那就是面試官的必殺技,這也是取這個標題的緣由,固然,本文的目的是爲了讓你在以後的全部面試中能多一個必殺技,專門用來絕殺面試官!面試
本文的核心思想就是,緩存
當面試官問:bash
「請講一講Spring中的循環依賴。」的時候,微信
咱們到底該怎麼回答?ide
主要分下面幾點源碼分析
OK,鋪墊已經作完了,接下來咱們開始正文post
什麼是循環依賴?測試
從字面上來理解就是A依賴B的同時B也依賴了A,就像下面這樣ui
體現到代碼層次就是這個樣子this
固然,這是最多見的一種循環依賴,比較特殊的還有
雖然體現形式不同,可是實際上都是同一個問題----->循環依賴
在回答這個問題以前首先要明確一點,Spring解決循環依賴是有前置條件的
出現循環依賴的Bean必需要是單例
依賴注入的方式不能全是構造器注入的方式(不少博客上說,只能解決setter方法的循環依賴,這是錯誤的)
其中第一點應該很好理解,第二點:不能全是構造器注入是什麼意思呢?咱們仍是用代碼說話
在上面的例子中,A中注入B的方式是經過構造器,B中注入A的方式也是經過構造器,這個時候循環依賴是沒法被解決,若是你的項目中有兩個這樣相互依賴的Bean,在啓動時就會報出如下錯誤:
爲了測試循環依賴的解決狀況跟注入方式的關係,咱們作以下四種狀況的測試
具體的測試代碼跟簡單,我就不放了。從上面的測試結果咱們能夠看到,不是隻有在setter方法注入的狀況下循環依賴才能被解決,即便存在構造器注入的場景下,循環依賴依然被能夠被正常處理掉。
那麼究竟是爲何呢?Spring究竟是怎麼處理的循環依賴呢?不要急,咱們接着往下看
關於循環依賴的解決方式應該要分兩種狀況來討論
結合了AOP的循環依賴
簡單的循環依賴(沒有AOP)
咱們先來分析一個最簡單的例子,就是上面提到的那個demo
經過上文咱們已經知道了這種狀況下的循環依賴是可以被解決的,那麼具體的流程是什麼呢?咱們一步步分析
首先,咱們要知道Spring在建立Bean的時候默認是按照天然排序來進行建立的,因此第一步Spring會去建立A。
與此同時,咱們應該知道,Spring在建立Bean的過程當中分爲三步
實例化,對應方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法
屬性注入,對應方法:AbstractAutowireCapableBeanFactory的populateBean方法
初始化,對應方法:AbstractAutowireCapableBeanFactory的initializeBean
這些方法在以前源碼分析的文章中都作過詳細的解讀了,若是你以前沒看過個人文章,那麼你只須要知道
實例化,簡單理解就是new了一個對象
屬性注入,爲實例化中new出來的對象填充屬性
初始化,執行aware接口中的方法,初始化方法,完成AOP代理
基於上面的知識,咱們開始解讀整個循環依賴處理的過程,整個流程應該是以A的建立爲起點,前文也說了,第一步就是建立A嘛!
建立A的過程實際上就是調用getBean方法,這個方法有兩層含義
建立一個新的Bean
從緩存中獲取到已經被建立的對象
咱們如今分析的是第一層含義,由於這個時候緩存中尚未A嘛!
首先調用getSingleton(a)方法,這個方法又會調用getSingleton(beanName, true),在上圖中我省略了這一步
getSingleton(beanName, true)這個方法實際上就是到緩存中嘗試去獲取Bean,整個緩存分爲三級
singletonObjects,一級緩存,存儲的是全部建立好了的單例Bean
earlySingletonObjects,完成實例化,可是還未進行屬性注入及初始化的對象
singletonFactories,提早暴露的一個單例工廠,二級緩存中存儲的就是從這個工廠中獲取到的對象
由於A是第一次被建立,因此無論哪一個緩存中必然都是沒有的,所以會進入getSingleton的另一個重載方法getSingleton(beanName, singletonFactory)。
調用getSingleton(beanName, singletonFactory)
這個方法就是用來建立Bean的,其源碼以下:
上面的代碼咱們主要抓住一點,經過createBean方法返回的Bean最終被放到了一級緩存,也就是單例池中。
那麼到這裏咱們能夠得出一個結論:一級緩存中存儲的是已經徹底建立好了的單例Bean
以下圖所示:
在完成Bean的實例化後,屬性注入以前Spring將Bean包裝成一個工廠添加進了三級緩存中,對應源碼以下:
這裏只是添加了一個工廠,經過這個工廠(ObjectFactory)的getObject方法能夠獲得一個對象,而這個對象實際上就是經過getEarlyBeanReference這個方法建立的。那麼,何時會去調用這個工廠的getObject方法呢?這個時候就要到建立B的流程了。
當A完成了實例化並添加進了三級緩存後,就要開始爲A進行屬性注入了,在注入時發現A依賴了B,那麼這個時候Spring又會去getBean(b),而後反射調用setter方法完成屬性注入。
由於B須要注入A,因此在建立B的時候,又會去調用getBean(a),這個時候就又回到以前的流程了,可是不一樣的是,以前的getBean是爲了建立Bean,而此時再調用getBean不是爲了建立了,而是要從緩存中獲取,由於以前A在實例化後已經將其放入了三級緩存singletonFactories中,因此此時getBean(a)的流程就是這樣子了
從這裏咱們能夠看出,注入到B中的A是經過getEarlyBeanReference方法提早暴露出去的一個對象,還不是一個完整的Bean,那麼getEarlyBeanReference到底幹了啥了,咱們看下它的源碼
它實際上就是調用了後置處理器的getEarlyBeanReference,而真正實現了這個方法的後置處理器只有一個,就是經過@EnableAspectJAutoProxy註解導入的AnnotationAwareAspectJAutoProxyCreator。也就是說若是在不考慮AOP的狀況下,上面的代碼等價於:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; return exposedObject;}複製代碼
也就是說這個工廠啥都沒幹,直接將實例化階段建立的對象返回了!因此說在不考慮AOP的狀況下三級緩存有用嗎?講道理,真的沒什麼用,我直接將這個對象放到二級緩存中不是一點問題都沒有嗎?若是你說它提升了效率,那你告訴我提升的效率在哪?
那麼三級緩存到底有什麼做用呢?不要急,咱們先把整個流程走完,在下文結合AOP分析循環依賴的時候你就能體會到三級緩存的做用!
到這裏不知道小夥伴們會不會有疑問,B中提早注入了一個沒有通過初始化的A類型對象不會有問題嗎?
答:不會
這個時候咱們須要將整個建立A這個Bean的流程走完,以下圖:
從上圖中咱們能夠看到,雖然在建立B時會提早給B注入了一個還未初始化的A對象,可是在建立A的流程中一直使用的是注入到B中的A對象的引用,以後會根據這個引用對A進行初始化,因此這是沒有問題的。
以前咱們已經說過了,在普通的循環依賴的狀況下,三級緩存沒有任何做用。三級緩存實際上跟Spring中的AOP相關,咱們再來看一看getEarlyBeanReference的代碼:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject;}複製代碼
若是在開啓AOP的狀況下,那麼就是調用到AnnotationAwareAspectJAutoProxyCreator的getEarlyBeanReference方法,對應的源碼以下:
回到上面的例子,咱們對A進行了AOP代理的話,那麼此時getEarlyBeanReference將返回一個代理後的對象,而不是實例化階段建立的對象,這樣就意味着B中注入的A將是一個代理對象而不是A的實例化階段建立後的對象。
看到這個圖你可能會產生下面這些疑問
在給B注入的時候爲何要注入一個代理對象?
答:當咱們對A進行了AOP代理時,說明咱們但願從容器中獲取到的就是A代理後的對象而不是A自己,所以把A看成依賴進行注入時也要注入它的代理對象
明明初始化的時候是A對象,那麼Spring是在哪裏將代理對象放入到容器中的呢?
在完成初始化後,Spring又調用了一次getSingleton方法,這一次傳入的參數又不同了,false能夠理解爲禁用三級緩存,前面圖中已經提到過了,在爲B中注入A時已經將三級緩存中的工廠取出,並從工廠中獲取到了一個對象放入到了二級緩存中,因此這裏的這個getSingleton方法作的時間就是從二級緩存中獲取到這個代理後的A對象。exposedObject == bean能夠認爲是一定成立的,除非你非要在初始化階段的後置處理器中替換掉正常流程中的Bean,例如增長一個後置處理器:
@Componentpublic class MyPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("a")) { return new A(); } return bean; }}複製代碼
不過,請不要作這種騷操做,徒增煩惱!
初始化的時候是對A對象自己進行初始化,而容器中以及注入到B中的都是代理對象,這樣不會有問題嗎?
答:不會,這是由於不論是cglib代理仍是jdk動態代理生成的代理類,內部都持有一個目標類的引用,當調用代理對象的方法時,實際會去調用目標對象的方法,A完成初始化至關於代理對象自身也完成了初始化
三級緩存爲何要使用工廠而不是直接使用引用?換而言之,爲何須要這個三級緩存,直接經過二級緩存暴露一個引用不行嗎?
答:這個工廠的目的在於延遲對實例化階段生成的對象的代理,只有真正發生循環依賴的時候,纔去提早生成代理對象,不然只會建立一個工廠並將其放入到三級緩存中,可是不會去經過這個工廠去真正建立對象
咱們思考一種簡單的狀況,就以單首創建A爲例,假設AB之間如今沒有依賴關係,可是A被代理了,這個時候當A完成實例化後仍是會進入下面這段代碼:
// A是單例的,mbd.isSingleton()條件知足// allowCircularReferences:這個變量表明是否容許循環依賴,默認是開啓的,條件也知足// isSingletonCurrentlyInCreation:正在在建立A,也知足// 因此earlySingletonExposure=trueboolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));// 仍是會進入到這段代碼中if (earlySingletonExposure) { // 仍是會經過三級緩存提早暴露一個工廠對象 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}複製代碼
看到了吧,即便沒有循環依賴,也會將其添加到三級緩存中,並且是不得不添加到三級緩存中,由於到目前爲止Spring也不能肯定這個Bean有沒有跟別的Bean出現循環依賴。
假設咱們在這裏直接使用二級緩存的話,那麼意味着全部的Bean在這一步都要完成AOP代理。這樣作有必要嗎?
不只沒有必要,並且違背了Spring在結合AOP跟Bean的生命週期的設計!Spring結合AOP跟Bean的生命週期自己就是經過AnnotationAwareAspectJAutoProxyCreator這個後置處理器來完成的,在這個後置處理的postProcessAfterInitialization方法中對初始化後的Bean完成AOP代理。若是出現了循環依賴,那沒有辦法,只有給Bean先建立代理,可是沒有出現循環依賴的狀況下,設計之初就是讓Bean在生命週期的最後一步完成代理而不是在實例化後就立馬完成代理。
如今咱們已經知道了三級緩存的真正做用,可是這個答案可能還沒法說服你,因此咱們再最後總結分析一波,三級緩存真的提升了效率了嗎?分爲兩點討論:
沒有進行AOP的Bean間的循環依賴
從上文分析能夠看出,這種狀況下三級緩存根本沒用!因此不會存在什麼提升了效率的說法
進行了AOP的Bean間的循環依賴
就以咱們上的A、B爲例,其中A被AOP代理,咱們先分析下使用了三級緩存的狀況下,A、B的建立流程
假設不使用三級緩存,直接在二級緩存中
上面兩個流程的惟一區別在於爲A對象建立代理的時機不一樣,在使用了三級緩存的狀況下爲A建立代理的時機是在B中須要注入A的時候,而不使用三級緩存的話在A實例化後就須要立刻爲A建立代理而後放入到二級緩存中去。對於整個A、B的建立過程而言,消耗的時間是同樣的
綜上,不論是哪一種狀況,三級緩存提升了效率這種說法都是錯誤的!
面試官:」Spring是如何解決的循環依賴?「
答:Spring經過三級緩存解決了循環依賴,其中一級緩存爲單例池(singletonObjects),二級緩存爲早期曝光對象earlySingletonObjects,三級緩存爲早期曝光對象工廠(singletonFactories)。當A、B兩個類發生循環引用時,在A完成實例化後,就使用實例化後的對象去建立一個對象工廠,並添加到三級緩存中,若是A被AOP代理,那麼經過這個工廠獲取到的就是A代理後的對象,若是A沒有被AOP代理,那麼這個工廠獲取到的就是A實例化的對象。當A進行屬性注入時,會去建立B,同時B又依賴了A,因此建立B的同時又會去調用getBean(a)來獲取須要的依賴,此時的getBean(a)會從緩存中獲取,第一步,先獲取到三級緩存中的工廠;第二步,調用對象工工廠的getObject方法來獲取到對應的對象,獲得這個對象後將其注入到B中。緊接着B會走完它的生命週期流程,包括初始化、後置處理器等。當B建立完後,會將B再注入到A中,此時A再完成它的整個生命週期。至此,循環依賴結束!
面試官:」爲何要使用三級緩存呢?二級緩存能解決循環依賴嗎?「
答:若是要使用二級緩存解決循環依賴,意味着全部Bean在實例化後就要完成AOP代理,這樣違背了Spring設計的原則,Spring在設計之初就是經過AnnotationAwareAspectJAutoProxyCreator這個後置處理器來在Bean生命週期的最後一步來完成AOP代理,而不是在實例化後就立馬進行AOP代理。