【spring原始碼系列】之【Bean的迴圈依賴】

小豬爸爸發表於2021-07-27

希望之光永遠向著目標清晰的人敞開。

1. 迴圈依賴概述

迴圈依賴通俗講就是迴圈引用,指兩個或兩個以上物件的bean相互引用對方,A依賴於B,B依賴於A,最終形成一個閉環。

Spring迴圈依賴的場景有兩種:

  • 構造器的迴圈依賴
  • field 屬性的迴圈依賴
    對於構造器的迴圈依賴,Spring 是無法解決,只能丟擲 BeanCurrentlyInCreationException 異常;對於field 屬性的迴圈依賴,Spring 只解決 scope 為 singleton 的迴圈依賴,對於scope 為 prototype 的 bean Spring 無法解決,直接丟擲 BeanCurrentlyInCreationException 異常。下面重點分析屬性依賴的情況。

2. 迴圈依賴執行流程


以上流程圖針對如下程式碼進行演示:

@Component
public class CircularRefA {

    public CircularRefA() {
        System.out.println("============CircularRefA()===========");
    }

    //這裡會觸發CircularRefB型別的getBean操作
    @Autowired
    private CircularRefB circularRefB;
}
@Component
public class CircularRefB {

    public CircularRefB() {
        System.out.println("============CircularRefB()===========");
    }

    //又會觸發A的getBean操作
    @Autowired
    private CircularRefA circularRefA;
}

step1: A類例項化執行,第一次三個快取中都沒有,會走doGetBean一路走到createBeanInstance,完成無參建構函式例項化,並在addSingletonFactory中設定三級快取;
step2:A類populateBean進行依賴注入,隨後觸發了B類屬性的getBean操作;
step3: B類與A類類似,第一次三個快取中也都沒有,無參建構函式例項化後,設定三級快取,將自己加入三級快取;
step4:B類populateBean進行依賴注入,這裡觸發了A類屬性的getBean操作;
step5: A類之前正在建立,此時已經是第二次進入,由於一級二級快取中都沒有,會從三級快取中獲取,並且允許 bean提前暴露則從三級快取中拿到物件工廠,從工廠中拿到物件成功後,升級到二級快取,並刪除三級快取;若以後有別的類引用的話就從二級快取中進行取;
step6:B類拿到了A的提前暴露例項注入到A類屬性中了,此時完成B類的例項化;
step7:A類之前依賴B類,B的例項化完成,進而促使A的例項化也完成,並且此時A的類B屬性已經有值,A類繼續走後續的afterSingletonCreateion與addSingleton方法,刪除正在建立快取中的例項,並將例項從二級快取移入以及快取,同時刪除二三級快取;

以上是A類例項化的全過程,下面會針對原始碼逐一分析。

3. 原始碼分析

首先建立A的例項,需要從A的getBean方法開始,到doGetBean,第一次優先從快取中取,進入getSingleton方法:

	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 從一級快取singletonObjects中取
		Object singletonObject = this.singletonObjects.get(beanName);
		// 一級快取為空,且單例物件正在建立
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				// 從二級快取earlySingletonObjects中取
				singletonObject = this.earlySingletonObjects.get(beanName);
				// 如果二級快取也為空,且允許bean提前暴露
				if (singletonObject == null && allowEarlyReference) {
					// 從三級快取singletonFactories中取
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					// 如果三級快取不為空
					if (singletonFactory != null) {
						// 呼叫getEarlyBeanReference方法提前暴露bean
						singletonObject = singletonFactory.getObject();
						// 提前暴露的bean放入二級快取,
						this.earlySingletonObjects.put(beanName, singletonObject);
						// 將提前暴露的bean從三級快取中刪除
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

這個方法主要是從三個快取中獲取,分別是:singletonObjectsearlySingletonObjectssingletonFactories,三者定義如下:

	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

意義如下:

  • singletonObjects:單例物件的cache
  • singletonFactories : 單例物件工廠的cache
  • earlySingletonObjects :提前暴露的單例物件的Cache

解決迴圈依賴的關鍵是三個快取,其中一級快取singletonObjects存放完全例項化的物件,物件以及其依賴的屬性都有值;二級快取earlySingletonObjects存放半例項化的物件,相當於在記憶體開闢了空間,已完成建立,但是還未進行屬性賦值,可以提前暴露使用;三級快取singletonFactories為物件工廠,用來建立提前暴露的bean並放入二級快取中。

首次初始化A時,三個快取中都沒有物件,會進入如下getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法,該方法第二個引數是個函式式介面,當內部呼叫getObject方法時,會呼叫createBean方法:

				// 建立bean例項
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					......
				

先進入getSingleton方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				// bean單例建立前
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					// 呼叫createBean方法建立bean
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					// 建立完成後要從正在例項化的bean集合singletonsCurrentlyInCreation中刪除該bean
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					// bean加入快取
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

該方法完成流程圖中的四個步驟:
step1:bean單例建立前,將beanName放入singletonsCurrentlyInCreation快取;
step2: singletonFactory.getObject()呼叫外面函式式介面中的createBean方法建立bean;
step3afterSingletonCreation方法將beanName從singletonsCurrentlyInCreation快取刪除,表示已建立完;
step4: 例項化後加入一級快取,二三級快取刪除。

以上1、3、4涉及程式碼如下:

	protected void beforeSingletonCreation(String beanName) {
		// 將正在建立的bean放入快取singletonsCurrentlyInCreation
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}
	protected void afterSingletonCreation(String beanName) {
		// 正在建立中的快取容器singletonsCurrentlyInCreation清除剛剛建立的bean
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}
	protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			// 一級快取存放bean
			this.singletonObjects.put(beanName, singletonObject);
			// 三級快取移除bean
			this.singletonFactories.remove(beanName);
			// 二級快取移除bean
			this.earlySingletonObjects.remove(beanName);
			//
			this.registeredSingletons.add(beanName);
		}
	}

其中step2流程較長,當A第一次例項化時,走到建立無參建構函式例項化createBeanInstance,隨後會走addSingletonFactory方法:

	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

從這段程式碼我們可以看出singletonFactories這個三級快取才是解決迴圈依賴的關鍵,該程式碼在createBeanInstance方法之後,也就是說這個bean其實已經被建立出來了,但是它還不是很完美(沒有進行屬性填充和初始化),但是對於其他依賴它的物件而言已經足夠了(可以根據物件引用定位到堆中物件),能夠被認出來了,所以Spring在這個時候選擇將該物件提前曝光出來讓大家認識認識。當三級快取有值後,後面如果再次用到該bean的時候,會從三級快取中,並通過提前暴露,升級到二級快取中,到這裡我們發現三級快取singletonFactories和二級快取earlySingletonObjects中的值都有出處了,那一級快取在哪裡設定的呢?就是在A建立完,並把A依賴的屬性B也建立完後,B有依賴於A,再次進入A後,A直接從二級快取中獲取,從而促使B物件建立完,隨即A也就建立完成,A完成createBean後走上面的step4中的addSingletion方法,完成一級快取的設定。

4.總結

Spring在建立bean的時候並不是等它完全完成,而是在建立過程中將建立中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 快取中),這樣一旦下一個 bean 建立的時候需要依賴 bean ,則直接使用 ObjectFactory 的 getObject() 獲取了,故在快取中使用三級快取獲取到例項,並將例項升級到二級快取,供後續例項如需二次使用時,可直接從二級快取中取,待例項完全建立後,升級到一級快取,並清理二級三級快取,總而言之提前暴露三級快取,以及一二三級快取的綜合使用是解決迴圈依賴的關鍵,各級快取各司其職,又能夠相互呼應,spring的設計實在精妙,給我們自己設計專案提供了一種優秀的思考方式。

相關文章