面試題1. Spring中bean的循環依賴怎麼解決?
(一). 首先說一下什麼是Spring的循環依賴:
- 其實就是在進行getBean的時候,A對象中去依賴B對象,而B對象又依賴C對象,但是對象C又去依賴A對象,結果就造成A、B、C三個對象都不能完成實例化,出現了循環依賴。就會出現死循環,最終導致內存溢出的錯誤。
(二).如何去解決Spring的循環依賴呢?
1.先知道什麼是Spring的“三級緩存”:就是下面的三個大的Map對象,因爲Spring中的循環依賴的理論基礎其實是基於java中的引用傳遞的,然後其實Spring中的單例對象的創建是分爲三個步驟的:
- createBeanInstance,其實第一步就是通過構造方法去進行實例化對象。但是這一步只是實例對象而已,並沒有把對象的屬性也給注入進去
- 然後這一步就是進行注入實例對象的屬性,也就是從這步對spring xml中指定的property進行populate
- 最後一步其實是初始化XML中的init方法,來進行最終完成實例對象的創建。但是AfterPropertiesSet方法會發生循環依賴的步驟集中在第一步和第二步。
singletonObjects指單例對象的cache (一級緩存)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
singletonFactories指單例對象工廠的cache(三級緩存)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
earlySingletonObjects指提前曝光的單例對象的cache(二級緩存)
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
2. 然後是怎麼具體使用到這個三級緩存的呢,或者說三級緩存的思路?
- 首先第一步是在Spring中會先去調用getSingleton(String beanName, boolean allowEarlyReference)來獲取想要的單例對象。
- 然後第一步會先進行通過singletonObjects這個一級緩存的集合中去獲取對象,如果沒有獲取成功的話並且使用isSingletonCurrentlyInCreation(beanName)去判斷對應的單例對象是否正在創建中(也就是說當單例對象沒有被初始化完全,走到初始化的第一步或者第二的時候),如果是正在創建中的話,會繼續走到下一步
- 然後會去從earlySingletonObjects中繼續獲取這個對象,如果又沒有獲取到這個單例對象的話,並且通過參數傳進來的allowEarlyReference標誌,看是不是允許singletonFactories(三級緩存集合)去拿到該實例對象,如果allowEarlyReference爲Ture的話,那麼繼續下一步
- 此時上一步中並沒有從earlySingletonObjects二級緩存集合中拿到想要的實例對象,最後只能從三級緩存singletonFactories (單例工廠集合中)去獲取實例對象,
- 然後把獲取的對象通過Put(beanName, singletonObject)放到earlySingletonObjects(二級緩存中),然後在再從singletonFactories(三級緩存)對象中的集合中把該對象給remove(beanName)出去。
- 附上核心代碼
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
從一級緩存獲取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
從二級緩存獲取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
從三級緩存獲取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);}
3. 總結一下爲什麼這麼做就能解決Spring中的循環依賴問題。
- 其實在沒有真正創建出來一個實例對象的時候,這個對象已經被生產出來了,雖然還不完美(還沒有進行初始化的第二步和第三步),但是已經能被人認出來了(根據對象引用能定位到堆中的對象),所以Spring此時將這個對象提前曝光出來讓大家認識,讓大家使用
- A首先完成了初始化的第一步,並且將自己提前曝光到singletonFactories中,此時進行初始化的第二步,發現自己依賴對象B,此時就嘗試去get(B),發現B還沒有被create,所以走create流程,B在初始化第一步的時候發現自己依賴了對象A,於是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒有,因爲A還沒初始化完全),嘗試二級緩存earlySingletonObjects(也沒有),嘗試三級緩存singletonFactories,由於A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A對象(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A對象後順利完成了初始化階段1、2、3,完全初始化之後將自己放入到一級緩存singletonObjects中。此時返回A中,A此時能拿到B的對象順利完成自己的初始化階段2、3,最終A也完成了初始化,長大成人,進去了一級緩存singletonObjects中,而且更加幸運的是,由於B拿到了A的對象引用,所以B現在hold住的A對象也蛻變完美了!一切都是這麼神奇!!
總結:Spring通過三級緩存加上“提前曝光”機制,配合Java的對象引用原理,比較完美地解決了某些情況下的循環依賴問題!