Spring 循環引用(三)源碼深刻分析版

@java

前言

關於Spring 循環引用 網上的分析文章不少,寫的水平參差不齊,雖然看完了 知道怎麼個回事 可是過段時間仍是忘記了,主要本人沒過目不忘的本領哈,可是隻要記住主要的點就行了spring

可是若是你本身想更深刻的瞭解,仍是要本身去看源碼分析一波,由於別人分析的時候,有些知識點你是get不到的,只有當本身走進源碼去看的時候,纔有get到更多的!好比網上不少文章都分析Springs是怎麼解決循環依賴的 可是爲何只有單類的才能夠,Prototype的就不行呢,在哪裏不行,或者說構造器的注入爲何也不能夠,最後若是解決循環依賴,或者說 怎麼去換中寫法去解決問題。緩存

紙上得來終覺淺 絕知此事要躬行! 這句話獻給正在讀文章的你,看完記得點贊,還有就是本身去下載Spring 源碼 去看看app

正文

OK,進入正文,固然上面也不是廢話啦,Spring 的循環引用 我想讀者們應該知道,不知道的話,算了 來個code把!ide

@Component
public class CycleTestServiceA {  
  private CycleTestServiceB b;
  public void setB(CycleTestServiceB b) {
    this.b = b;
  }
}

@Component
public class CycleTestServiceB {  
  private CycleTestServiceA a;
  public void setA(CycleTestServiceA a) {
    this.a = a;
  }
}

上面的 代碼 就是一個普通的set注入的方式,A裏面依賴B,B裏面依賴A,這樣就致使了循環依賴,Component默認是Singleton的函數

分析

咱們從Spring Beanc建立開始做爲入口,在Spring IoC 容器中一個完整的Bean 要進過實例化 和初始化的階段源碼分析

Spring Bean 實例化就getBean的過程ui

那咱們接進入源碼去看下getBean的過程this

doGetBean

getBean方法時 BeanFactory 接口的方法 他的實現類有不少,咱們跟進去他的抽象實現類org/springframework/beans/factory/support/AbstractBeanFactory.java 類,其實都是調用了doGetBean方法prototype

下面是我截取的核心代碼

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

   	final String beanName = transformedBeanName(name);
   	Object bean;

      	/*
       * 檢測是否 有緩存對象  這個方法時處理循環依賴的關鍵入口
       * 記住這個的代碼 我還會回來的
      	* */
   	Object sharedInstance = getSingleton(beanName);
   	if (sharedInstance != null && args == null) {
   		if (logger.isDebugEnabled()) {
   			if (isSingletonCurrentlyInCreation(beanName)) {
   				logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
   						"' that is not fully initialized yet - a consequence of a circular reference");
   			}
   			else {
   				logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
   			}
   		}
   		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   	}
   	else {
        	/*
   		*Prototype bean 是否在建立當中 若是存在 說明產生了循環依賴  處理Bean 循環依賴的地方
   		*這個地方就是爲何Scope 是Prototype的時候 會報循環依賴的錯誤,慢慢看 後面會解釋
   		* */
   		if (isPrototypeCurrentlyInCreation(beanName)) {
   			throw new BeanCurrentlyInCreationException(beanName);
   		}

   	    ...

   		if (!typeCheckOnly) {
   			markBeanAsCreated(beanName);//這個方法就是把當前的bean 加入到alreadyCreated的set集合中 後面有些判斷須要
   		}

   		try {
   		    ...
   			
   			/*
   			* 獲取Bean 的依賴項 這邊的依賴 是咱們在xml 有時候能夠配置的depends-on的依賴 和咱們本次講的循環依賴不是同一個
   			* 我特別說明下
   			* */
   			String[] dependsOn = mbd.getDependsOn();
   			if (dependsOn != null) {
   				for (String dep : dependsOn) {
   				  //註冊依賴 建立Bean 等
   				}
   			}

   			/*
   			* 若是是單列 建立createBean  記住這個的代碼 我還會回來的
   			* */
   			if (mbd.isSingleton()) {
   				sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
   					@Override
   					public Object getObject() throws BeansException {
   						try {
   							return createBean(beanName, mbd, args);
   						}
   						catch (BeansException ex) {
   						  ...
   						}
   					}
   				});
   				bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
   			}

   			/*
   			* Prototype對象  
   			* */
   			else if (mbd.isPrototype()) {
   				// It's a prototype -> create a new instance.
   				Object prototypeInstance = null;
   				try {
   					beforePrototypeCreation(beanName);
   					prototypeInstance = createBean(beanName, mbd, args);
   				}
   				finally {
   					afterPrototypeCreation(beanName);
   				}
   				bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
   			}
   			/*
   			* 不是Singleton也不是Prototype,多是自定義scope的對象
   			* */
   			else {
   			   ...
   			}
   		}
   	}
       ...
   	return (T) bean;
   }

上面是dogetBean()的核心方法

爲何Prototype不能夠

帶着這個問題 咱們能夠從上面的代碼中 看下 Spring在處理麼Prototype的時候 有2個方法beforePrototypeCreation(),afterPrototypeCreation(),
上下代碼

/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
			new NamedThreadLocal<Object>("Prototype beans currently in creation");

    protected void beforePrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal == null) {
			this.prototypesCurrentlyInCreation.set(beanName);
		}
		else if (curVal instanceof String) {
			Set<String> beanNameSet = new HashSet<String>(2);
			beanNameSet.add((String) curVal);
			beanNameSet.add(beanName);
			this.prototypesCurrentlyInCreation.set(beanNameSet);
		}
		else {
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.add(beanName);
		}
	}
	
	protected void afterPrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal instanceof String) {
			this.prototypesCurrentlyInCreation.remove();
		}
		else if (curVal instanceof Set) {
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.remove(beanName);
			if (beanNameSet.isEmpty()) {
				this.prototypesCurrentlyInCreation.remove();
			}
		}
	}

上面的代碼 我相信小夥伴都能看的懂,就是用一個set集合存儲當前正在建立的Bean的BeanName,並且是用ThreadLocal去存儲Set集合的 ThreadLocal是每一個線程私有的。看到這個 咱們再把目光往代碼上面看一看 isPrototypeCurrentlyInCreation這個方法的判斷

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}

看到了麼 這邊就是用這個ThreadLocal裏面的set集合去判斷的,爲何用ThreadLocal想下,你想呀,A依賴B,而B依賴A,AB都是Prototype的,A建立的時候 A會加入到這個set集合中,而後A去填充實例的時候,由於要依賴B,因此去getB,發現B又依賴A,這個時候有要getA,你看 當執行到 最上面的判斷isPrototypeCurrentlyInCreation的時候,是不報了循環引用的錯,由於A已經在prototypesCurrentlyInCreation的Set集合中了,由於整個流程必定是一個線程走下去的,因此存入ThreadLocal中,一點問題沒有,並且還不受其餘線程影響~

createBean

不論是哪一種Scope 都是要調用createBean方法的,咱們跟進去代碼 發現惟一重寫的實如今org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java 中
咱們進入代碼看下

protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
		RootBeanDefinition mbdToUse = mbd;
	    ...
		try {
			// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
			// 該函數的做用是給 BeanPostProcessors 後置處理器返回一個代理對象的機會
			// 這裏是實現AOP處理的重要地方
			// AOP是經過BeanPostProcessor機制實現的,而接口InstantiationAwareBeanPostProcessor是實現代理的重點
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		...

		/*
		* 後置處理器 沒有返回有效的bean 就建立
		* */
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isDebugEnabled()) {
			logger.debug("Finished creating instance of bean '" + beanName + "'");
		}
		return beanInstance;
	}

這邊 我看到一句英文註釋,都沒捨得替換中文,Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. 哈哈 給後置處理器一個返回代理bean的機會,這邊就是Spring 中實現AOP的重點,動態代理 其實就是使用後置處理器 替換了target Bean 的實例,從而達到代理的做用,這個之後聊到AOP 的時候在慢慢聊吧!這個最核心的代碼還在再doCreateBean中,繼續跟進

doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
			throws BeanCreationException {

        // Instantiate the bean.
		BeanWrapper instanceWrapper = null;//BeanWrapper 是Bean 的包裝類  方便對Bean 實例的操做
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
		Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
		mbd.resolvedTargetType = beanType;

		....
		
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));//知足三個條件  單列  運行循環引用  bean 是否正在建立中
		if (earlySingletonExposure) {
			addSingletonFactory(beanName, new ObjectFactory<Object>() {
				@Override
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);//提早暴露引用  獲取早期的引用
				}
			});
		}

		// Initialize the bean instance. 初始化Bean 實例
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);//填充Bean
			if (exposedObject != null) {
				exposedObject = initializeBean(beanName, exposedObject, mbd);//執行初始化Bean裏面的方法
			}
		}
		...

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
			   /*
			   *這邊其實仍是作了一個判斷,exposedObject是通過了 initializeBean方法方法的 
			   *而bean仍是那個提早暴露的Bean,
			   *爲何要作這個判斷你,是由於exposedObject通過了initializeBean裏面的後置處理器的修改 可能Object 已經改變了 
			   **/
				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);
						}
					}
					/*
					*有興趣的能夠根據到上面的每個方法看下 ,這邊就是判斷若是提早暴露的bean已經和在後置處理器裏面修改了而且不同了,就拋出異常,由於提早暴露的Bean 可能做爲了另外的bean的依賴 這樣就會致使單類的bean在容器中有2個實例的出現,這是非法的!
					*/
					if (!actualDependentBeans.isEmpty()) {
					  //拋出一個異常 因爲不少文字我就刪掉了
					}
				}
			}
		}
	   ...
		return exposedObject;
	}

earlySingletonExposure這個主要關注的是earlySingletonExposure 這邊的代碼,這個就是在Bean 實例化完成後,開始填充屬性之間發的代碼
earlySingletonExposure爲true 要知足三個條件

  • 單類
  • 容許循環引用
  • 當時單類正在建立中

前面2個能夠理解 那最後一個又是什麼呢?話很少說 進入方法看下

private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
public boolean isSingletonCurrentlyInCreation(String beanName) {
		return this.singletonsCurrentlyInCreation.contains(beanName);
	}

這個方法 一看很簡答 就是判斷當前的bean是否在singletonsCurrentlyInCreation的set集合中 那這個集合又是何時加入的呢?帶着這個想法 我又重頭掃描了一篇代碼 還記的org/springframework/beans/factory/support/AbstractBeanFactory.java代碼中的doGetBean()方法裏面Singleton的Bean 在建立Instance的時候是調用了getSingleton方法麼,不清楚的話 能夠往上看下

getEarlyBeanReference

這個方法 是 addSingletonFactory 方法 構建ObjectFactory的參數的時候 裏面返回使用方法
看下代碼:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
					if (exposedObject == null) {
						return null;
					}
				}
			}
		}
		return exposedObject;
	}

裏面最主要的就是看下getEarlyBeanReference方法 這個方法時SmartInstantiationAwareBeanPostProcessor裏面的方法,他的實現有2個 一個是org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java 還有一個是動態代理使用的 我就不列舉了,

public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

看了下 其實InstantiationAwareBeanPostProcessorAdapter的重寫就是 返回了當前的bean 沒有作任何操做。這邊其實就是作了一個引用的保存。

getSingleton

代碼位於org/springframework/beans/factory/support/DefaultListableBeanFactory.java中

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
			    ...
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
			    ...
				try {
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				finally {
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return (singletonObject != NULL_OBJECT ? singletonObject : null);
		}
	}

這個方法其所就是SingletonBean的核心建立流程

beforeSingletonCreation

protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

當我看到這個singletonsCurrentlyInCreation.add的時候 我很欣慰 由於我以前的問題解決了 就是這個方法把Bean 放入到以前的singletonsCurrentlyInCreation的集合中的

singletonFactory.getObject

這個應該都很清楚了 就是咱們方法傳入的匿名的ObjectFactory對象,當以前getObject的時候 纔會執行咱們剛纔的看的createBean方法

afterSingletonCreation

protected void afterSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

看下afterSingletonCreation方法裏面的東西也很簡單,就是從singletonsCurrentlyInCreation集合中移除

addSingleton

先看下代碼

protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
			this.singletonFactories.remove(beanName);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}

看到 這邊 就不得表介紹下三級緩存了

  • singletonObjects 實例化 初始化都完成的bean 緩存
  • earlySingletonObjects 可提早引用的 Bean 緩存,這裏面的Bean 是一個非完整的bean,屬性填充 後置處理器都未執行的bean
  • singletonFactories 單類bean的建立工廠函數對象

說道這裏 咱們就清楚了 這個方法其所就是在二級緩存和三級緩存中刪除當前的Bean,把當前的Bean 放入到一級緩存中,由於到了這一步 bean 的實例化,屬性填充,後置處理器執行,初始化等方法都已經執行了。

addSingletonFactory

這個方法 哪裏用的呢 那咱們有要回到上面的代碼doCreateBean中 當earlySingletonExposure爲true的時候 會調用這個方法addSingletonFactory
這個方法就是 當前的Bean能夠提早引用的話執行的方法
看下代碼也很簡答

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

看下這個方法 說白了就是往三級緩存裏面存放bean的ObjectFactory對象 這個地方也是處理循環引用的關鍵,這個時候Bean 剛剛進行了實例化 尚未進行bean的屬性填充和初始化等一些列方法

那怎麼去解決提早引用的呢?能夠看下ObjectFactory返回的是getEarlyBeanReference對象

getSingleton(beanName)

這個方法是在doGetBean方法中 從緩存中獲取Bean 對象的方法 這個方法很關鍵 是處理循環依賴的入口,那咱們跟進去看下方法

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

最終調用的方法如上,allowEarlyReference是爲true的,咱們仍是用最上面的ServiceA和ServiceB 爲例,第一次ServiceA 進入的時候 是無法進入下面的判斷的 應爲當前ServiceA不在SingletonCurrentlyInCreation中,可是當第二次進來,第二次是何時呢,就是在填充ServiceB的時候 須要依賴 ServiceB,這個時候ServiceB也要執行getBean的流程,發現又依賴ServiceA,這個時候 ServiceA就是在SingletonCurrentlyInCreation的集合中了,並且在三級緩存中,這個時候會進行判斷條件裏面的方法,先找一級緩存,找不到就找二級緩存,最後找三級緩存,而後將取出三級緩存裏面的ObjectFactory執行getObject方法 就是獲取咱們上面提到的提早引用的bean,最後將bean 放入到二級緩存,從三級緩存中移除~

核心說明

看完了 上面的一推 也許很懵逼,可能也是我文字組織能力差,只能之後慢慢改變

緩存的說明

上面涉及到幾個緩存 我在邊在重寫描述一下

名稱 類型 使用說明 所屬類
singletonObjects Map<String, Object> 實例化 初始化都完成的bean的緩存 DefaultSingletonBeanRegistry.java
earlySingletonObjects Map<String, Object> 可提早引用的 Bean 緩存,這裏面的Bean 是一個非完整的bean,屬性填充 後置處理器都未執行的bean DefaultSingletonBeanRegistry.java
singletonFactories Map<String, ObjectFactory<?>> 單類bean的建立工廠函數對象 DefaultSingletonBeanRegistry.java
singletonsCurrentlyInCreation Set 立刻要建立的單類Bean的集合 DefaultSingletonBeanRegistry.java
prototypesCurrentlyInCreation ThreadLocal object 是一個Set 立刻要建立的prototype的Bean的集合 AbstractBeanFactory.java
alreadyCreated Set 至少建立過一次的Bean 在提早暴露的bean修改了致使不一致時 判斷會用到 AbstractBeanFactory.java

執行流程圖

最終我仍是用一個方法執行的流程圖 來描述下 循環依賴的處理

執行流程圖1
執行流程圖2

構造器的注入解決

那麼爲何構造器的注入方式不行呢?緣由是由於 Bean在實例化階段的時候createBeanInstance的時候就會去建立依賴的B,這樣的話A根本就走不到提早暴露的代碼塊,因此會報一個循環引用的錯誤,報錯的地方就是構造函數參數bean 建立的地方,本身能夠寫個demo,調試下 在哪一步報錯,博主但是看了半天 才找到,哈哈!

解決方法

關於若是解決構造器的循環注入
https://www.baeldung.com/circular-dependencies-in-spring
這是一篇外國博文,小夥伴們能夠看下

  • 使用懶加載
  • 修改使用setter注入的方式
  • 使用PostConstruct註解
  • InitializingBean 後置處理器的方式

總結

Spring 處理循環依賴的核心就是 三級緩存,讓Bean 提早暴露出來,能夠提早引用,讓互相依賴的Bean 能夠流程上執行下去,從而解決了循環依賴的問題

最後的最後 仍是本身對照源碼 本身理解一遍,我相信必定會加深你的理解,必定會有收穫

碼字不易,花了一個週末的時間,各位看官喜歡的話點個贊,鼓勵下博主,繼續創造,多謝~

相關文章
相關標籤/搜索