天天看點

spring--解決循環依賴

    首先看下spring建立一個bean的簡單流程,假如beanA引用beanB,beanB引用beanA,spring在初始化beanA的時候會造成循環依賴(這裡講的是單例,spring底層隻對單例循環依賴進行解決)。

spring--解決循環依賴

       在記錄之前我寫了兩個測試類進還原循環依賴,一個是ClassA,裡面的引用了ClassB,同時ClassB也引用了ClassA。這樣子ClassA和ClassB形成了循環依賴。

spring--解決循環依賴
spring--解決循環依賴

         spring容器底層在建立ClassB Bean會調用getSingleton先去從一級緩存singletonObjects中拿,如果一級緩存沒有,則去二級緩存earlySingletonObjects中拿,二級緩存中沒有,則去三級緩存singletonFactories中拿,如果都沒有,則調用createBean方法開始建立這個ClassB這個Bean。

spring--解決循環依賴

       在調用 createbean之前spring會調用isPrototypeCurrentlyInCreation方法來判斷目前這個bean存在正在建立中的緩存中(prototypesCurrentlyInCreation),如果不是則放入該緩存中,用于後面循環依賴的解決。因為ClassB是第一次被spring容器加載,是以肯定是空,這時候被放入正在建立中的緩存中。

spring--解決循環依賴

        接着createbean會調用doCreateBean方法,這個真正執行建立bean的方法。該方法調用createBeanInstance(beanName, mbd, args)通過後置處理器判斷調用ClassB的構造方法并建立傳回ClassB的執行個體對象,此時對象的ClassA的引用肯定是空,因為ClassB的預設構造方法,并沒有對ClassA指派。接着spring會将這個早期對象放入三級緩存singletonFactories中。

spring--解決循環依賴

      放入三級緩存singletonFactories結束後,調用populateBean方法進行屬性指派。populateBean中會去判斷ClassB這個Bean有哪些屬性以及屬性的類型并選擇調用哪個方法來進行指派,如果是引用類型則調用resolveReference方法進行屬性指派。

spring--解決循環依賴

       進入resolveReference方法,spring會先去判斷目前容器是否有父容器,如果有則從父容器中擷取引用對象ClassA,如果沒有則從目前容器中擷取引用對象ClassA。(spring允許子容器使用父容器的bean,就是在這裡展現出來,比如springmvc)這時候程式會調用this.beanFactory.getBean(resolvedName)。從容器中再次擷取ClassA這個引用對象。此時getBean會調用doGetBean從新走剛才建立ClassB對象流程。

spring--解決循環依賴
spring--解決循環依賴

      同樣spring在建立ClassB的引用對象ClassA時,也會去解析ClassA的引用對象。此時ClassA的引用對象是ClassB(此時ClassB對象是暴露在三級緩存中的),這是時候通用調用目前容器的getBean--->doGetBean,在doGetBean中調用getSingleton(beanName)方法。在這裡我們會看到spring在從二級或三級緩存中擷取對象是有條件的,條件即使這個對象正在建立中。通過isSingletonCurrentlyInCreation(beanName)這個方法去判斷的。

spring--解決循環依賴

       确實剛建立ClassB的時候記錄了ClassB正在建立中,這時候從一級緩存中擷取(肯定沒有因為ClassB還沒完全執行個體化完),接着繼續從二級緩存中擷取也沒有(因從剛才分析下來,ClassB隻單單暴露在三級緩存中),這時候調用singletonObject = singletonFactory.getObject()這個方法,從三級緩存中拿到了早期對象。singletonFactory.getObject()會進一步調用getObject()方法中的getEarlyBeanReference,這裡面調用了spring的後置處理器,給開發者對早期對象能夠進行提前的修改。比如ClassB 對象裡有String str="";可以提前對str進行操作。是以為什麼spring有二級緩存就可以解決循環依賴。還要用到三級緩存。spring在三級緩存給開發提供了給早期對象擴充的功能。此時将擴充完的早期對象ClassB放入二級緩存,移除三級緩存,并将這個對象傳回出去。

spring--解決循環依賴

      此時ClassA對象ClassB的引用已經拿到值了,是以建立一直往下走,走到addSingleton(beanName, singletonObject)這個方法,将ClassA放入一級緩存singletonObjects,從二級緩存和三級緩存中移除。因為ClassA的bean已經建立完成了,二級緩存和三級緩存已經沒用了。此時完整的ClassA的bean傳回出去,ClassB的屬性ClassA也得到了指派,ClassB的bean可以繼續建立了。

spring--解決循環依賴

     注意:spring在通過構造器給屬性指派是無法解決循環依賴的,從上面分析來看spring執行createBeanInstance方法去判斷調用目前bean的構造方法,此時還未放入三級緩存中。如果是bean作用域是原型也是無法解決循環依賴,因為原型對象并沒有放在緩存中。