Spring循環依賴如何解決

網上關於spring循環依賴的介紹有不少,寫的也很是好,文末的參考文章中列舉了我看到的認爲寫的很不錯的文章,此篇文章主要是一個總結性說明。本身也寫了一個測試demo,詳見 https://github.com/gsonkeno/spring-training/tree/master/springbean-circular-dependencyjava

構造器循環依賴

先看一段構造器循環依賴的demo日誌git

// 開始建立bean staffA
.support.DefaultSingletonBeanRegistry 213 getSingleton - Creating shared instance of singleton bean 'studentA'
.support.AbstractAutowireCapableBeanFactory 460 createBean - Creating instance of bean 'studentA'

// 由於staffA的構造器方法參數依賴一個staffB,因此開始建立 staffB
.support.DefaultSingletonBeanRegistry 213 getSingleton - Creating shared instance of singleton bean 'studentB'
.support.AbstractAutowireCapableBeanFactory 460 createBean - Creating instance of bean 'studentB'

// 由於staffB的構造器方法參數依賴一個staffC,因此開始建立 staffC
.support.DefaultSingletonBeanRegistry 213 getSingleton - Creating shared instance of singleton bean 'studentC'
.support.AbstractAutowireCapableBeanFactory 460 createBean - Creating instance of bean 'studentC'

// 由於staffC的構造器方法參數依賴一個staffA,因此開始建立 staffA
.support.DefaultSingletonBeanRegistry 213 getSingleton - Creating shared instance of singleton bean 'studentA'

.support.AbstractApplicationContext 556 refresh - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'studentA' defined in file ...

由於建立staffA—>staffB—>staffC,再從staffC—>staffA的時候,spring檢測到staffA正在建立中,直接拋出異常。關鍵代碼github

public class DefaultSingletonBeanRegistry
	protected void beforeSingletonCreation(String beanName) {
	    // 若是beanName當前正在處理中,說明遇到了環,直接拋異常
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}
}

setter循環依賴(singleton)

先看一段setter循環依賴(singleton)的demo日誌,每行日誌中頭部不重要的信息已刪除:web

// 開始建立單例bean(staffA)
.support.DefaultSingletonBeanRegistry 213 getSingleton - Creating shared instance of singleton bean 'staffA'
.support.AbstractAutowireCapableBeanFactory 460 createBean - Creating instance of bean 'staffA'

// 檢測到staffA上的注入方法setStaffB
.annotation.InjectionMetadata 74 checkConfigMembers - Registered injected element on class [com.gsonkeno.circular.dependency.staff.StaffA]: AutowiredMethodElement for public void com.gsonkeno.circular.dependency.staff.StaffA.setStaffB(com.gsonkeno.circular.dependency.staff.StaffB)

// 過早地把staffA緩存起來,爲可能出現的循環依賴提供解決辦法
.support.AbstractAutowireCapableBeanFactory 563 doCreateBean - Eagerly caching bean 'staffA' to allow for resolving potential circular references

// 開始處理staffA上的注入方法setStaffB
.annotation.InjectionMetadata 88 inject - Processing injected element of bean 'staffA': AutowiredMethodElement for public void com.gsonkeno.circular.dependency.staff.StaffA.setStaffB(com.gsonkeno.circular.dependency.staff.StaffB)

// 開始建立單例staffB
.support.DefaultSingletonBeanRegistry 213 getSingleton - Creating shared instance of singleton bean 'staffB'
.support.AbstractAutowireCapableBeanFactory 460 createBean - Creating instance of bean 'staffB'

// 檢測到staffB上的注入方法setStaffC
.annotation.InjectionMetadata 74 checkConfigMembers - Registered injected element on class [com.gsonkeno.circular.dependency.staff.StaffB]: AutowiredMethodElement for public void com.gsonkeno.circular.dependency.staff.StaffB.setStaffC(com.gsonkeno.circular.dependency.staff.StaffC)

// 過早地把staffB緩存起來,爲可能出現的循環依賴提供解決辦法
.support.AbstractAutowireCapableBeanFactory 563 doCreateBean - Eagerly caching bean 'staffB' to allow for resolving potential circular references

// 開始處理staffB上的注入方法setStaffC
.annotation.InjectionMetadata 88 inject - Processing injected element of bean 'staffB': AutowiredMethodElement for public void com.gsonkeno.circular.dependency.staff.StaffB.setStaffC(com.gsonkeno.circular.dependency.staff.StaffC)

// 開始建立單例staffC
.support.DefaultSingletonBeanRegistry 213 getSingleton - Creating shared instance of singleton bean 'staffC'
.support.AbstractAutowireCapableBeanFactory 460 createBean - Creating instance of bean 'staffC'

// 檢測到staffC上的注入方法setStaffA
.annotation.InjectionMetadata 74 checkConfigMembers - Registered injected element on class [com.gsonkeno.circular.dependency.staff.StaffC]: AutowiredMethodElement for public void com.gsonkeno.circular.dependency.staff.StaffC.setStaffA(com.gsonkeno.circular.dependency.staff.StaffA)

// 過早地把staffC緩存起來,爲可能出現的循環依賴提供解決辦法
.support.AbstractAutowireCapableBeanFactory 563 doCreateBean - Eagerly caching bean 'staffC' to allow for resolving potential circular references

// 開始處理staffC上的注入方法setStaffA
.annotation.InjectionMetadata 88 inject - Processing injected element of bean 'staffC': AutowiredMethodElement for public void com.gsonkeno.circular.dependency.staff.StaffC.setStaffA(com.gsonkeno.circular.dependency.staff.StaffA)

// 從循環依賴環中,獲取到了早早緩存的了單例staffA,儘管它尚未徹底初始化
.support.AbstractBeanFactory 250 doGetBean - Returning eagerly cached instance of singleton bean 'staffA' that is not fully initialized yet - a consequence of a circular reference

// 裝配staffC所依賴的staffA到staffC上去
.annotation.AutowiredAnnotationBeanPostProcessor 527 registerDependentBeans - Autowiring by type from bean name 'staffC' to bean named 'staffA'

// 結束建立staffC
.support.AbstractAutowireCapableBeanFactory 497 createBean - Finished creating instance of bean 'staffC'

// 裝配staffB所依賴的StaffC到staffB上去
.annotation.AutowiredAnnotationBeanPostProcessor 527 registerDependentBeans - Autowiring by type from bean name 'staffB' to bean named 'staffC'

// 結束建立staffB
.support.AbstractAutowireCapableBeanFactory 497 createBean - Finished creating instance of bean 'staffB'

// 裝配staffA所依賴的staffB到staffA上去
.annotation.AutowiredAnnotationBeanPostProcessor 527 registerDependentBeans - Autowiring by type from bean name 'staffA' to bean named 'staffB'

// 結束建立staffA
.support.AbstractAutowireCapableBeanFactory 497 createBean - Finished creating instance of bean 'staffA'
// 上面的日誌是建立staffA時打出來的,能夠認爲staffB、staffC這兩個bean是在建立staffA的過程當中
// 被迫建立的,因此等到建立staffB、staffC時,因爲已經建立過,直接從緩存中拿去便可
.support.AbstractBeanFactory 254 doGetBean - Returning cached instance of singleton bean 'staffB'
.support.AbstractBeanFactory 254 doGetBean - Returning cached instance of singleton bean 'staffC'

如何判斷一個單例bean正在建立中呢? 主要在於singletonsCurrentlyInCreation,以下:面試

public class DefaultSingletonBeanRegistry{
	/** Names of beans that are currently in creation */
	private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}

在bean建立前(確定在執行構造器方法前),會把它放進去,建立bean完成後,會放出來。主要邏輯已經在上面的日誌中體現了出來,若是有興趣,深刻debug一遍理解的會更加清楚。spring

關鍵詞:
解決bean的依賴前提早暴露bean的獲取方式,建立中的bean進行緩存,遞歸深刻緩存

setter循環依賴(prototype)

spring不支持原型bean屬性注入循環依賴,不一樣於構造器注入循環依賴會在建立spring容器context時報錯,它會在用戶執行代碼如context.getBean()時拋出異常。由於對於原型bean,spring容器只有在須要時纔會實例化,初始化它。svg

由於spring容器不緩存prototype類型的bean,使得沒法提早暴露出一個建立中的bean。spring容器在獲取prototype類型的bean時,若是由於環的存在,檢測到當前線程已經正在處理該bean時,就會拋出異常。核心代碼學習

public abstract class AbstractBeanFactory{
	/** Names of beans that are currently in creation */
	private final ThreadLocal<Object> prototypesCurrentlyInCreation =
			new NamedThreadLocal<>("Prototype beans currently in creation");
			
	protected boolean isPrototypeCurrentlyInCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		// 若是beanName已經存在於正在處理中的prototype類型的bean集中,後面會拋出異常
		return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}
}

另外補充一句,三個bean中,只要有一個bean是singleton模式,就不會拋出異常。測試

參考

  1. Spring源碼學習–Bean對象循環依賴問題解決(四)https://blog.csdn.net/qq924862077/article/details/73926268
  2. 知乎面試官會問關於spring的哪些問題 https://www.zhihu.com/question/39814046/answer/550590260