天天看點

Spring源碼之循環依賴前言一、什麼是循環依賴二、循環依賴的解決

文章目錄

  • 前言
  • 一、什麼是循環依賴
    • 1. 循環依賴有幾種情況
  • 二、循環依賴的解決
    • 1.getSingleton(beanName)
    • 2 為什麼不能解決構造函數注入方式的循環依賴
    • 2.疑問

前言

上篇我們講了bean的初始化流程,要搞懂循環依賴,必須要先清除流程,不清楚的請移步上一篇文章。

這篇我們就來講講Spring是如何解決循環依賴的

一、什麼是循環依賴

循環依賴,其實就是循環引用,就是兩個或者兩個以上的 bean 互相引用對方,最終形成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A。如下圖所示:

Spring源碼之循環依賴前言一、什麼是循環依賴二、循環依賴的解決

循環依賴,其實就是一個死循環的過程,在初始化 A 的時候發現引用了 B,這時就會去初始化 B,然後又發現 B 引用 C,跑去初始化 C,初始化 C 的時候發現引用了 A,則又會去初始化 A,依次循環永不退出,除非有終結條件。

1. 循環依賴有幾種情況

有兩種

  • 基于構造函數的注入方式的循環依賴
  • 基于屬性注入的循環依賴

Spring隻能解決屬性注入的循環依賴,構造函數注入方式的循環依賴是沒法解決的,原因等我們分析完循環依賴後再來個總結.

其實倒不是說不能使用構造函數注入,而且Spring從4.3開始推薦使用構造函數注入的方式,舉例具體來說

A 依賴 B, B又依賴A

下面這幾種情況

  • A中采用構造函數注入依賴B, B中采用構造函數依賴A, 無法解決
  • A中采用構造函數注入依賴B, B中采用屬性注入A, 可以解決
  • A中采用屬性注入依賴B, B中采用構造函數依賴A 無法解決
  • A中采用屬性注入依賴B. B中采用屬性注入A 可以解決

細心的異能已經發現了,隻要B中采用構造函數注入A,就無法解決,A采用什麼方式則沒什麼影響

下面我們以A,B 都采用屬性注入的方式且無AOP代理的情況下來看Spring是如何解決循環依賴的,這也是最常見的方式

二、循環依賴的解決

1.getSingleton(beanName)

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}
           

話不多說,直接上圖,看不清的點選放大來看

Spring源碼之循環依賴前言一、什麼是循環依賴二、循環依賴的解決

但是這隻是最簡單的循環依賴的一種情況, 有AOP的會更加複雜,以後有機會再來更新

2 為什麼不能解決構造函數注入方式的循環依賴

如果熟悉了循環依賴的解決流程,那麼這個問題自然也很好回答, 因為如果采用構造函數注入的方式,那麼會去看構造函數的參數是否初始化,如果沒有會初始化這個bean,如此一來,便陷入了死循環,沒法提前曝光半初始化的bean了,

說的簡單點就是,指定了構造函數就不會走預設的無參構造,就無法走下面的提前曝光,加入三級緩存,也就無法解決循環依賴了

2.疑問

這個疑問我在上篇文章中提過,就是這段代碼

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<>(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 " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}
           

這裡也與循環依賴有關

//到這裡又會走getSingleton(beanName, false),是不是很熟悉,沒錯就是我們進doGetBean時一開始會走的從緩存中取bean的方法

//不過之前傳的是true,這裡傳的是false

//先提前說明下這個方法的流程,舉例說明

//對于循環依賴: A依賴B, B依賴A,且屬性注入(構造函數注入無法解決嘛)

//初始化A, 走到填充屬性時,會去初始化B, 由于B中又依賴的A, 又去getBean(A), 此時就會走 getSingleton(beanName, true)

//而此時容器中一,二級緩存中沒有A執行個體的,隻有三級緩存中有,就會把三級緩存中那個半初始化的beanA取出(注意:此時會把A放在的二級緩存中,從三級緩存中移除)

//然後B的屬性填充完畢, 然後走初始化完成, 當B走到下面這段代碼時, 即走到getSingleton(beanName, false)時, 一二級緩存均為空,

//但是傳參是false,是以Object earlySingletonReference 為null,那麼傳回的就是初始化完成的exposedObject

//即exposedObject = initializeBean(beanName, exposedObject, mbd);

// 最後後會把初始化好的B放入一級緩存中

//然後繼續走之前的A屬性填充(留意一下,A填充好了),初始化,但是當A走到這段代碼時,因為之前往二級緩存中放了A,

//是以此時取二級緩存不為空,會傳回緩存中的這個引用,而且如果這個引用和增強後的bean是一個對象執行個體,那麼會傳回這個引用

//此時有人可能會有疑問: 緩存中的這個bean不是半初始化的麼,怎麼傳回使用了呢?

//這是因為,二級緩存中存放的是A的引用,在之前這個引用指向的對象執行個體被填充了啊,這就是之前讓你留意一下的原因

//不過讀到這裡我有個疑問: 為什麼Spring要再去緩存中取一下這個bean呢?我直接使用初始化傳回的那個bean嘛,就是exposedObject,

//為什麼要現在這樣設計呢?還查下緩存,然後再比較bean有沒有變,變了傳回exposedObject, 沒變傳回緩存中的bean

//我看有的部落格說的是此處代碼的作用是: 循環依賴檢查,判斷是否需要抛出異常

//存疑?有知道的小夥伴可以留言告訴我,我感覺這裡是和AOP代理有關

繼續閱讀