spring 源碼03

spring中bean的生命週期

Springbean的生命週期.png

spring bean的做用域

spring bean的做用域.png

spring 如何解決循環依賴

@Component
public class A {

    @Autowired
    private B b;

    public void aMethod(){
        b.bMethod();
    }
}

@Component
public class B {
    @Autowired
    private A a;

    public void bMethod(){
        System.out.println("bmethod");
    }
}

@SpringBootApplication
public class MyspringlearningApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext context = SpringApplication.run(MyspringlearningApplication.class, args);
		A a = context.getBean(A.class);
		a.aMethod();

	}
}

複製代碼

上面的示例中,A依賴B,B又依賴了A.是典型的循環依賴的狀況,可是運行的結果來看,程序能正常運行,調用a.aMethod()能打印出"bmethod"字符串
那spring是怎麼解決循環依賴的呢???spring

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

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

	// Eagerly check singleton cache for manually registered singletons.
	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 {
		// Fail if we're already creating this bean instance: // We're assumably within a circular reference.
		if (isPrototypeCurrentlyInCreation(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
		.......
		各類做用域的建立bean
}
複製代碼

獲取bean的時候緩存

  1. 從spring維護的三個緩存中獲取bean
  2. 若是緩存中獲取失敗,而且bean處於建立中,拋出循環依賴異常
  3. 在對應的做用域中建立bean

spring爲bean建立了三個級別的緩存, 這三級緩存分別是bash

級別 名稱 類型 內容
1 singletonObjects Map<String, Object> 正在完成加載的bean
2 earlySingletonObjects Map<String, Object> 完成了實例化,提早曝光的Bean02
3 singletonFactories Map<String, ObjectFactory<?>> 提早曝光的bean01
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;
}
複製代碼

getBean的第一步操做就是從三個級別緩存中獲取beanapp

  1. 若是singletonObjects中有,能夠直接返回
  2. 若是earlySingletonObjects中有,直接返回
  3. 若是singletonFactories中有
    1. 調用ObjectFactory的getObject方法
    2. 將獲取到的bean放入二級緩存中
    3. 從三級緩存中刪除該beanName

在bean完成實例化以後,populateBean以前,會將bean提早曝光。加入到三級緩存中函數

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

	.....
	createBeanInstance

	// Eagerly cache singletons to be able to resolve circular references
	// even when triggered by lifecycle interfaces like BeanFactoryAware.
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		if (logger.isDebugEnabled()) {
			logger.debug("Eagerly caching bean '" + beanName +
					"' to allow for resolving potential circular references");
		}
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	....
	populateBean()
	....
}
複製代碼

循環依賴的解決.png

當對beanB執行populateBean填充屬性的時候,由於A已經加入到三級緩存中了,因此在getBean的最開始就能從緩存中獲取到已經完成實例化的beanA,完成beanB的屬性填充。ui

什麼狀況下的循環依賴問題能夠被spring解決

首先,咱們經常使用的依賴注入方式有以下幾種this

  1. 構造函數注入
@Component
public class C {

    private D d;
    public C(@Autowired D d) {
    }

    public void cMethod(){
        d.dMethod();
    }
}

@Component
public class D {
    private C c;
    public D(@Autowired C c) {
        this.c = c;
    }

    public void dMethod(){
        System.out.println("dMethod");
    }
}
@SpringBootApplication
public class MyspringlearningApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext context = SpringApplication.run(MyspringlearningApplication.class, args);
		C c = context.getBean(C.class);
		c.cMethod();

	}
}

複製代碼

運行結果:報循環依賴異常spa

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  c defined in file [/Users/lihongli/testWorkSpace/myspringlearning/target/classes/com/li/myspringlearning/C.class]
↑     ↓
|  d defined in file [/Users/lihongli/testWorkSpace/myspringlearning/target/classes/com/li/myspringlearning/D.class]
└─────┘

複製代碼
  1. 屬性注入
@Component
public class A {

    @Autowired
    private B b;

    public void aMethod(){
        b.bMethod();
    }
}

@Component
public class B {
    @Autowired
    private A a;

    public void bMethod(){
        System.out.println("bmethod");
    }
}
複製代碼

運行正常debug


按照前面的代碼分析,spring會在完成實例化createBeanInstance方法執行完成後,纔會把bean提早曝光到緩存中,才能讓依賴方獲取到這個提早曝光的bean完成依賴方bean的加載。若是將依賴寫在構造函數中,會在實例化C的時候就去加載依賴的D,在D加載過程當中也會在他的構造函數中去加載C的bean,C當前處於建立中,可是沒有進行提早曝光,致使循環依賴異常code

若是將構造器注入的示例改爲以下:

@Component
public class C {
    @Autowired
    private D d;
//    public C(@Autowired D d) {
//    }

    public void cMethod(){
        d.dMethod();
    }
}

@Component
public class D {
    private C c;
    public D(@Autowired C c) {
        this.c = c;
    }

    public void dMethod(){
        System.out.println("dMethod");
    }
}
@SpringBootApplication
public class MyspringlearningApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext context = SpringApplication.run(MyspringlearningApplication.class, args);
		C c = context.getBean(C.class);
		c.cMethod();

	}
}
複製代碼

讓C在獲取D的bean以前,能把本身提早曝光出去,就不過報循環依賴了。

earlySingletonObjects和singletonFactories

在doCreateBean方法中,完成bean的實例化後,若是容許提早曝光,會把剛剛完成實例化的bean暴露出去

此時加入到三級緩存緩存中。目的是爲了在調用getSingleton從三級緩存升級到二級緩存中的時候能執行一些拓展的操做

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}
複製代碼
相關文章
相關標籤/搜索