Spring 源碼(八)循環依賴

循環依賴是指兩個或者多個Bean以前相互持有對方。在Spring中循環依賴通常有三種方式:java

  1. 構造函數循環依賴
  2. setter方法循環依賴
  3. prototype 範圍的依賴處理

構造函數循環依賴

在Spring中構造函數循環依賴是沒法解決的,由於構造函數依賴實際上是方法間循環調用的一種,會發生死循環。可是在Spring中會直接拋出BeanCurrentlyInCreationException異常。源碼以下:git

// 在緩存中獲取Bean,若是沒有就建立Bean
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(beanName, "'beanName' must not be null");
	synchronized (this.singletonObjects) {
		// 在緩存中獲取Bean
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null) {
			// 判斷容器是否正在銷燬單實例Bean
			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!)");
			}
			// 將當前須要建立的Bean標示放到Set集合,若是失敗則拋出BeanCurrentlyInCreationException異常
			beforeSingletonCreation(beanName);
			boolean newSingleton = false;
			boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
			if (recordSuppressedExceptions) {
				this.suppressedExceptions = new LinkedHashSet<Exception>();
			}
			try {
				// 建立Bean實例
				singletonObject = singletonFactory.getObject();
				newSingleton = true;
			}
			...
			if (newSingleton) {
				// 將Bean實例註冊到singletonObjects容器中
				addSingleton(beanName, singletonObject);
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);
	}
}

protected void beforeSingletonCreation(String beanName) {
	// 將當前須要建立的Bean標示方法Set集合,若是失敗則拋出BeanCurrentlyInCreationException異常
	if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
		throw new BeanCurrentlyInCreationException(beanName);
	}
}

執行過程:github

  1. 從緩存中獲取Bean,若是沒有則走建立Bean流程
  2. 判斷容器是否正在銷燬單實例Bean,若是是則不建立Bean
  3. 將當前須要建立的Bean標示(name)放入Set集合中(當前正在建立的Bean池),若是放入失敗則拋出BeanCurrentlyInCreationException異常
  4. 建立Bean實例
  5. 將Bean實例註冊到容器(放到緩存中)

解決構造函數依賴主要是第3步實現的,Spring在容器建立的Bean的時候,會將Bean的標示(name)放到一個Set集合裏面(當前正在建立的Bean池)。當在建立Bean的過程當中,發現自已經在這個Set集合中時,就直接會拋出BeanCurrentlyInCreationException異常,而不會發生死循環。spring

setter方法循環依賴

@Service
public class AService {
    @Autowired
    private BService bService;

    @Autowired
    public void setbService(BService bService) {
        this.bService = bService;
    }
}

這兩種方式都算是setter方法依賴。咱們建立單實例Bean的大體過程能夠劃分紅三個階段:緩存

  1. 實例化 createBeanInstance(beanName, mbd, args);
  2. 設置屬性值 populateBean(beanName, mbd, instanceWrapper);
  3. 初始化 initializeBean(beanName, exposedObject, mbd);

對於Setter注入形成的循環依賴,Spring容器是在建立Bean第一步實例化後,就將Bean的引用提早暴露出來。經過提早暴露出一個單例工廠方法,從而使得其餘Bean能夠引用到該Bean。app

建立Bean時提早暴露剛完成第一步的Bean,源碼以下:ide

addSingletonFactory(beanName, new ObjectFactory<Object>() {
	@Override
	public Object getObject() throws BeansException {
		return getEarlyBeanReference(beanName, mbd, 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,源碼以下:函數

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

咱們能夠看到,在自動裝配Bean的過程當中,會去找三個緩存:spring-boot

  1. singletonObjects:存放完成建立的Bean全部步驟的單實例Bean
  2. earlySingletonObjects:存放只完成了建立Bean的第一步,且是由單實例工廠建立的Bean
  3. singletonFactories:存放只完成了建立Bean的第一步後,提早暴露Bean的單實例工廠。

這裏爲何會用一個三級緩存呢,爲啥不直接將只完成了建立Bean的第一步的Bean直接方到earlySingletonObjects緩存中呢?this

咱們跟入 getEarlyBeanReference(beanName, mbd, bean)咱們能夠發現,單實例工廠方法返回Bean的時候還執行了後置處理器的,在後置處理器中咱們還能夠對Bean進行一些特殊處理。若是咱們直接將剛完成實例化的Bean放入earlySingletonObjects緩存中,那麼失去對Bean進行特殊處理的機會。

源碼以下:

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

對於singleton 做用域 bean ,能夠經過setAllowCircularReferences(false);來禁用循環引用。

prototype 範圍的依賴處理

對於prototype 做用域 bean, Spring 容器沒法完成依賴注入,由於 Spring 容器不進行緩存prototype做用域的 bean ,所以沒法提早暴露一個建立中的 bean 示。

源碼

https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases

spring-boot-student-spring 工程

參考

《Spring源碼深度解析》

相關文章
相關標籤/搜索