spring之我見- spring循環依賴解決方案

單例在spring裏的獲取方式

今天講一下spring中針對單例bean的循環依賴問題,本着追本溯源的學習理念,我們要先知道單例在spring中怎麼管理的。spring獲取實例都通過beanfactory的getBean方法獲取實例,順着代碼而下,在doGetBean方法(AbstractBeanFactory)中,單例總是通過getSingleton()方法獲取緩存實例。

protected <T> T doGetBean(
			final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
			throws BeansException {

......
		Object sharedInstance = getSingleton(beanName);
......
}

再來看看getSingleton方法,也非常簡單,spring給了三個map,網上說是三級緩存

  • 一級緩存 : singletonObjects 初始化完後的單例都會存在這個map中,一般ioc容器初始完後,你再用getbean取實例都可以從這裏獲取到。
  • 二級緩存 :earlySingletonObjects 提前曝光的實例,這時候bean還處於創建中(bean的創建分爲 實例化 和 初始化),沒有完全創建好。
  • 三級緩存 :singletonFactories 跟二級緩存一樣,bean還處於萌芽期,甚至取到的只是一個工廠類,需要通過getObject獲取實例。

這段代碼的含義就是 先從一級緩存中獲取實例,取不到,再去取二級緩存,再沒有,那就三級中取。 注意: 當從三級中取到實例時,會刪除三級緩存中的值,然後放入二級緩存

有人問設計多級緩存的含義在哪? 這其實是爲了 本文的中心思想:spring循環依賴解決方案 服務的。後面再來細講。

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);
	}

循環依賴的問題

上一章我們講了單例如何從三個緩存中去取,可能爲什麼要設計這個緩存我們聽的雲裏霧裏,這裏我們就拋出問題:循環依賴。

我們知道,當A 有一個 屬性 B , B 有一個屬性A時,就形成了循環依賴,按照單例bean的創建邏輯,A沒創建完,就去拿B,B還沒創建完,又去拿A,請問A在哪?A還沒創建完呢 ! 所以解決問題的關鍵在哪?spring給設計了一個多級緩存,當A完成實例化(createBeanInstance)時,這個實例已經有了自己的內存地址,只是對象不夠完善,spring先把這種對象放進一個緩存。然後裝配屬性的時候需要B時 ,B開始自己的創建之旅,途中B需要A了,它就可以從緩存中順利拿到實例A,即使這時候的A並不完善。 然後B把自己創建完成後,又輪到A繼續完善自己,直到 A和B 都創建完畢,這樣就沒有了循環依賴的問題。

下面用圖反映spring bean的大致創建流程 與 多級緩存之間的配合:

  • 當實例化完一個初始bean時,會先放入三級緩存,記住,這裏的A還只是一個 初生的嬰兒,沒有屬性裝配,沒有經歷初始化。
  • 然後經歷裝配bean屬性的過程。期間屬性涉及到 循環依賴的時候,就可以通過第三緩存拿到對象。拿到後刪除三級緩存,放入二級緩存。
  • 實例創建完畢後會刪除二,三級緩存,放入一級緩存。一級緩存就是 spring單例對象的完全體,後面程序可以通過beanfactory 隨時取用。
    在這裏插入圖片描述

爲什麼要設計三層緩存

在瞭解了循環依賴的基本原理後,我當時第一個疑問就是:爲什麼要設計三層緩存?而不是兩層,理論上,設計兩層就能滿足需求。

我們來反過來看看最開始根據getSingleton獲取單實例的方法,有一個參數allowEarlyReference,這個爲true,就說明你可以從 三級緩存 (singletonFactories) 來獲取提前曝光的實例,如果爲false,你最多隻能從二級緩存(earlySingletonObjects)來獲取實例。那哪裏 會用false來調用getSingleton方法呢?

	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);
	}

我們從doCreateBean方法中看到如下代碼,這段代碼在裝配完bean後(populateBean)開始執行,會看到其中調用了getSingleton(beanName, false),這裏其實是 解決循環依賴中的二次檢查,我們舉個例子:A和B相互關聯,當A初始化的時候,發現需要B,然後A作爲提前曝光的bean放入三級緩存。然後創建B,B發現需要A,從三級緩存(singletonFactories)中順利取出,放入二級緩存(earlySingletonObjects),然後繼續創建B,B創建完畢,繼續初始化A,這時, A因爲被BeanPostProcessors處理過,導致返回了一個完全不一樣的對象,這時進入下面的代碼進行二次檢查,getSingleton因爲allowEarlyReference參數爲false 的緣故,只能最多從二級緩存中取數據,發現還真找到了,從二級緩存中取出也說明了 對象A存在被其它對象依賴的情況,當然也包括循環依賴的情形,對於循環依賴來說,對象的創建過程中不允許被二次修改,即使是BeanPostProcessors處理過也不能返回內存地址不一樣的對象,想想這樣也是對的,這樣才能保證全程的對象是一致的。 這也說明了二級緩存的作用--如果你能從二級緩存中拿到對象,就說明它是被其它對象依賴的,在經過BeanPostProcessors 等方法的處理後,也要二次檢查來保證對象的一致性。

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章