Spring核心之bean生命週期和三級快取
1 Spring核心
在使用spring
框架的日常開發中,bean
之間的迴圈依賴太頻繁了,spring
spring
是如何解決bean之間迴圈依賴,為什麼要使用到三級快取,而不是二級快取?
1.1 bean生命週期
首先大家需要了解一下bean
在spring
中的生命週期,bean
在spring
的載入流程,才能夠更加清晰知道spring
是如何解決迴圈依賴的。
1.1.1 bean生命週期中重要介面
我們在spring
的BeanFactory
工廠列舉了很多介面,代表著bean
的生命週期,我們主要記住的是圈紅線圈出來的介面, 再結合spring
的原始碼來看這些介面主要是在哪裡呼叫的
1.1.2 建立bean
AbstractAutowireCapableBeanFactory
doCreateBean
方法是建立bean
的開始,我們可以看到首先需要例項化這個bean
,也就是在堆中開闢一塊記憶體空間給這個物件,createBeanInstance
方法裡面邏輯大概就是採用反射生成例項物件,進行到這裡表示物件還並未進行屬性的填充,也就是@Autowired
註解的屬性還未得到注入
1.1.3 屬性填充
我們可以看到第二步就是填充bean
的成員屬性,populateBean
方法裡面的邏輯大致就是對使用到了注入屬性的註解就會進行注入,如果在注入的過程發現注入的物件還沒生成,則會跑去生產要注入的物件,第三步就是呼叫initializeBean
方法初始化bean
,也就是呼叫我們上述所提到的介面
1.1.4 初始化bean
可以看到initializeBean
方法中,首先呼叫的是使用的Aware
介面的方法,我們具體看一下invokeAwareMethods
方法中會呼叫Aware
介面的那些方法
1.1.4.1 Aware相關介面
我們可以知道如果我們實現了BeanNameAware
,BeanClassLoaderAware
,BeanFactoryAware
三個Aware
介面的話,會依次呼叫setBeanName(), setBeanClassLoader(), setBeanFactory()
方法,再看applyBeanPostProcessorsBeforeInitialization
原始碼
1.1.4.2 BeanPostProcessors相關介面
發現會如果有類實現了BeanPostProcessor
介面,就會執行postProcessBeforeInitialization
方法,這裡需要注意的是:如果多個類實現BeanPostProcessor
介面,那麼多個實現類都會執行postProcessBeforeInitialization
方法,可以看到是for
迴圈依次執行的,還有一個注意的點就是如果載入A類到spring
容器中,A類也重寫了BeanPostProcessor
介面的postProcessBeforeInitialization
方法,這時要注意A類的postProcessBeforeInitialization
方法並不會得到執行,因為A類還未載入完成,還未完全放到spring
的singletonObjects
一級快取中。
再看一個注意的點
可以看到ApplicationContextAwareProcessor
也實現了BeanPostProcessor
介面,重寫了postProcessBeforeInitialization
方法,方法裡面並呼叫了invokeAwareInterfaces
方法,而invokeAwareInterfaces
方法也寫著如果實現了眾多的Aware
介面,則會依次執行相應的方法,值得注意的是ApplicationContextAware
介面的setApplicationContext
方法,再看一下invokeInitMethods
原始碼
1.1.4.3 InitializingBean介面
發現如果實現了InitializingBean
介面,重寫了afterPropertiesSet
方法,則會呼叫afterPropertiesSet
方法,最後還會呼叫是否指定了init-method
,可以通過標籤,或者@Bean
註解的initMethod
指定,最後再看一張applyBeanPostProcessorsAfterInitialization
原始碼圖
1.1.4.4 BeanPostProcessors介面後置方法
發現跟之前的postProcessBeforeInitialization
方法類似,也是迴圈遍歷實現了BeanPostProcessor
的介面實現類,執行postProcessAfterInitialization
方法。整個bean
的生命執行流程就如上面截圖所示,哪個介面的方法在哪裡被呼叫,方法的執行流程。
1.1.5 bean生命週期總結
最後,對bean的生命流程進行一個流程圖的總結
或者看簡單版本:
1.2 三級快取
1.2.1 引言
上面對bean
的生命週期做了一個整體的流程分析,對spring
如何去解決迴圈依賴的很有幫助。前面我們分析到填充屬性時,如果發現屬性還未在spring
中生成,則會跑去生成屬性物件例項。
我們可以看到填充屬性的時候,spring
會提前將已經例項化的bean
通過ObjectFactory
半成品暴露出去,為什麼稱為半成品是因為這時候的bean
物件例項化,但是未進行屬性填充,是一個不完整的bean
例項物件
spring
利用singletonObjects, earlySingletonObjects, singletonFactories
三級快取去解決的,所說的快取其實也就是三個Map
1.2.2 三級快取各個存放物件
可以看到三級快取各自儲存的物件,這裡重點關注二級快取earlySingletonObjects
和三級快取singletonFactory
,一級快取可以進行忽略。前面我們講過先例項化的bean
會通過ObjectFactory
半成品提前暴露在三級快取中
singletonFactory
是傳入的一個匿名內部類,呼叫ObjectFactory.getObject()
最終會呼叫getEarlyBeanReference
方法。再來看看迴圈依賴中是怎麼拿其它半成品的例項物件的。
1.2.3 迴圈依賴
我們假設現在有這樣的場景AService
依賴BService
,BService
依賴AService
AService
首先例項化,例項化通過ObjectFactory
半成品暴露在三級快取中- 填充屬性
BService
,發現BService
還未進行過載入,就會先去載入BService
- 再載入
BService
的過程中,例項化,也通過ObjectFactory
半成品暴露在三級快取 - 填充屬性
AService
的時候,這時候能夠從三級快取中拿到半成品的ObjectFactory
拿到ObjectFactory
物件後,呼叫ObjectFactory.getObject()
方法最終會呼叫getEarlyBeanReference()
方法,getEarlyBeanReference
這個方法主要邏輯大概描述下如果bean
被AOP
切面代理則返回的是beanProxy
物件,如果未被代理則返回的是原bean例項
。
這時我們會發現能夠拿到bean
例項(屬性未填充),然後從三級快取移除,放到二級快取earlySingletonObjects
中,而此時B注入的是一個半成品的例項A物件,不過隨著B初始化完成後,A會繼續進行後續的初始化操作,最終B會注入的是一個完整的A例項,因為在記憶體中它們是同一個物件。
1.2.4 是否可以移除二級快取
我們發現這個二級快取好像顯得有點多餘,好像可以去掉,只需要一級和三級快取也可以做到解決迴圈依賴的問題
只要兩個快取確實可以做到解決迴圈依賴的問題,但是有一個前提這個bean
沒被AOP
進行切面代理,如果這個bean
被AOP
進行了切面代理,那麼只使用兩個快取是無法解決問題,下面來看一下bean
被AOP
進行了切面代理的場景
我們發現AService
的testAopProxy
被AOP
代理了,看看傳入的匿名內部類的getEarlyBeanReference
返回的是什麼物件。
發現singletonFactory.getObject()
返回的是一個AService
的代理物件,還是被CGLIB
代理的。再看一張再執行一遍singletonFactory.getObject()
返回的是否是同一個AService
的代理物件
我們會發現再執行一遍singleFactory.getObject()
方法又是一個新的代理物件,這就會有問題了,因為AService
是單例的,每次執行singleFactory.getObject()
方法又會產生新的代理物件。
假設這裡只有一級和三級快取的話,每次從三級快取中拿到singleFactory
物件,執行getObject()
方法又會產生新的代理物件,這是不行的,因為AService
是單例的,所有這裡我們要藉助二級快取來解決這個問題,將執行了singleFactory.getObject()
產生的物件放到二級快取中去,後面去二級快取中拿,沒必要再執行一遍singletonFactory.getObject()
方法再產生一個新的代理物件,保證始終只有一個代理物件。還有一個注意的點
既然singleFactory.getObject()
返回的是代理物件,那麼注入的也應該是代理物件,我們可以看到注入的確實是經過CGLIB
代理的AService
物件。所以如果沒有AOP
的話確實可以兩級快取就可以解決迴圈依賴的問題,如果加上AOP
,兩級快取是無法解決的,不可能每次執行singleFactory.getObject()
方法都給我產生一個新的代理物件,所以還要藉助另外一個快取來儲存產生的代理物件