1. 程式人生 > 其它 >Spring 迴圈依賴及解決方式

Spring 迴圈依賴及解決方式

迴圈依賴

Spring 有一個經典的問題,就是如何解決迴圈依賴,話不多說,直接開始,

@Componentpublic Class A {   @Autowired private B b;}@Componentpublic Class B {   @Autowired private A b;}

spring bean 的生命週期

獲取一個 Bean 的操作從 getBean(String name) 開始主要步驟為

1、getBean(String name)

2、例項化物件 A a = new A(); 此時執行構造方法的依賴注入

3、設定物件屬性 populateBean(beanName, mbd, instanceWrapper); 此時執行屬性的依賴注入

4、執行初始化方法 initializeBean(beanName, exposedObject, mbd); 此時執行 bean 的 initialize 方法

5、將生成好的 bean 物件新增到 單例池(一個 hashMap,保證單例 bean 在 context 僅僅存在一個物件)

6、結束

虛擬碼如下:

public Object getBean(String name) {	//省略根據name獲取A的過程	A a = new A();	a.initialze();	singletonObjects.put(name, a);	return a;}
A 依賴 B 的情況下的載入流程

虛擬碼如下:

public Object getBean(String name) {	//省略根據name獲取A的過程	A a = new A(); //例項化A	a.setB(getBean("B")); //設定屬性,發現a依賴於b,所以先載入b,載入B完成以後再繼續載入a	a.initialze(); //執行初始化方法	singletonObjects.put(name, a); //將a放入單例池中	return a;}
A、B 互相依賴的載入流程

以上就會出現一個問題,由於 a、b 都是單例 Bean,載入 b 的時候,到了上圖中標紅的階段後,b 依賴注入的 a 的引用應該是通過 getBean(A) 得到的引入,如果還是以上的邏輯,又再一次走入了 A 的建立邏輯,此時就是發生了迴圈依賴。下面我們就開始介紹 Spring 是如何解決迴圈依賴的。

一級快取:單例池 singletonObjects

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

我們都知道如果是單例的 Bean,每次 getBean(beanName)返回同一個 bean,也就是在整個 ApplicationContext 裡面,僅有一個單例 Bean,單例 Bean 建立完成後就放在 singletonObjects 這個 Map 裡面,這就是一級快取。此時說的“建立完成”指的是圖一的第 6 步驟,圖三中 getBean("B") 的過程中,a 是沒有加入到一級快取中,所以在 getBean("B") 的流程中,b 依賴了 a,此時 b 是找不到 a 物件的。依然會無法解決迴圈引用的問題。

二級快取:earlySingletonObjects

private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

這個時候我們考慮再引入一個 Map 存放引用,earlySingletonObjects 這個 map 我們打算存放提前暴露 bean 的引用,例項化以後,我們就把物件放入到 earlySingletonObjects 這個 map 中,這樣在 載入 b 的過程中,b.setA(getBean("a")),我們就可以在 earlySingletonObjects 拿到 a 的引用,此時 a 僅僅經過了例項化,並沒有設定屬性。流程如下:

1、getBean(A)

2、A a = new A();

3、earlySingletonObjects.put("a", a); 將 A 放入二級快取

3、設定 A 的屬性

4、getBean(B)

5、設定 B 的屬性,發現 B 依賴 A,從二級快取中獲取 A

6、載入 B 成功

7、將 B 放入一級快取

8、繼續載入 A

9、載入 A 完成,將 A 放入單例池

到目前為止,發現使用二級快取似乎就能解決我們的問題。看起來很美好,這是 Spring IOC 的特性,Spring 的另一大特性是 AOP 面向切面程式設計,動態增強物件,不管使用 JDK 的動態代理和 Cglib 動態代理,都會生成一個全新的物件。下圖中我標出了 AOP 動態增強的位置。

此時就會出現一個問題,因為經過 AOP 以後,生成的是增強後的 bean 物件,也就是一個全新的物件,java培訓我們可以看到經過圖中的流程後,單例池中會存在兩個 bean:增強後的 a、b 物件,此時 a 物件中依賴的 b 為增強後的,而 b 物件依賴的 a 是為原始物件,未增強的。所以使用二級快取解決不了迴圈依賴中發生過 aop 的引用問題。

三級快取:singletonFactories

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

為了解決二級快取中 AOP 生成新物件的問題,Spring 中的解決方案是:提前 AOP,如果我們能夠提前 AOP 就能解決上面的問題了,提前 AOP 指的就是,在 載入 B 的流程中,如果發生了迴圈依賴,就是說 b 又依賴了 a,我們就要對 a 執行 aop,提前獲取增強以後的 a 物件,這樣 b 物件依賴的 a 物件就是增強以後的 a 了。三級快取的 key 是 beanName,value 是一個 lambda 表示式,這個 lambda 表示式的作用就是進行提前 AOP。

下面是加入了三級快取和 AOP 的流程圖,PS:可能會有點亂。。。。。。

上面就是三級快取的作用,其中有個三級快取到二級快取的升級過程,這個非常重重要,這個主要是防止重複 aop。好的,寫到這裡,我們對 Spring 如何使用三級快取解決迴圈依賴的流程已經大概清楚了,下面分析一下原始碼。

原始碼解析:

1、 doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {    Object bean;    //首先先嚐試獲取bean,如果載入過就不會在重複載入了    Object sharedInstance = getSingleton(beanName);    //省略細節    if(sharedInstance != null) {        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);    } else {         //根據beanName獲取 beanDefinition 物件        final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);        if (mbd.isSingleton()) {            //單例bean的載入邏輯           sharedInstance = getSingleton(beanName, () -> {              try {                 return createBean(beanName, mbd, args);              }              catch (BeansException ex) {                 // Explicitly remove instance from singleton cache: It might have been put there                 // eagerly by the creation process, to allow for circular reference resolution.                 // Also remove any beans that received a temporary reference to the bean.                 destroySingleton(beanName);                 throw ex;              }           });           else if (mbd.isPrototype()) {               //原型域bean的載入邏輯               Object prototypeInstance = null;               try {                  beforePrototypeCreation(beanName);                  prototypeInstance = createBean(beanName, mbd, args);               }               finally {                  afterPrototypeCreation(beanName);               }               bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);            }           bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);        }    }    return (T)bean;}
2、 第 1 步中 getSingleton(beanName)
public Object getSingleton(String beanName) {   return getSingleton(beanName, true);}
protected Object getSingleton(String beanName, boolean allowEarlyReference) { //首先去一級快取中獲取如果獲取的到說明bean已經存在,直接返回 Object singletonObject = this.singletonObjects.get(beanName); //如果一級快取中不存在,則去判斷該bean是否在建立中,如果該bean正在建立中,就說明了,這個時候發生了迴圈依賴 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //如果發生迴圈依賴,首先去二級快取中獲取,如果獲取到則返回,這個地方就是獲取aop增強以後的bean singletonObject = this.earlySingletonObjects.get(beanName); //如果二級快取中不存在,且允許提前訪問三級引用 if (singletonObject == null && allowEarlyReference) { //去三級快取中獲取 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //如果三級快取中的lambda表示式存在,執行aop,獲取增強以後的物件,為了防止重複aop,將三級快取刪除,升級到二級快取中 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject;}
3、 第 1 步中 單例 bean 的載入邏輯
sharedInstance = getSingleton(beanName, () -> {  try {     return createBean(beanName, mbd, args);  }  catch (BeansException ex) {     // Explicitly remove instance from singleton cache: It might have been put there     // eagerly by the creation process, to allow for circular reference resolution.     // Also remove any beans that received a temporary reference to the bean.     destroySingleton(beanName);     throw ex;  }});
//獲取beanpublic Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { //將當前bean加入到 singletonsCurrentlyInCreation 這個map中,這個map裡面是正在建立中的bean,用於判斷迴圈依賴 beforeSingletonCreation(beanName); //執行上面方法的lambda表示式,建立bean singletonObject = singletonFactory.getObject(); //將 singletonsCurrentlyInCreation 裡面的這個bean刪除 afterSingletonCreation(beanName); //bean建立完成,將bean加入到單例池中 addSingleton(beanName, singletonObject); } return singletonObject; }}
4、核心方法,載入 bean
//createBean(beanName, mbd, args); 方法 建立bean的核心邏輯// 最終呼叫的是 AbstractAutowiredCapableBeanFactory.createBean 這個方法protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)      throws BeanCreationException {   // Instantiate the bean.   BeanWrapper instanceWrapper = null;   if (mbd.isSingleton()) {      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);   }   if (instanceWrapper == null) {      instanceWrapper = createBeanInstance(beanName, mbd, args);   }   //例項化,操作等同於 new 一個bean物件   final Object bean = instanceWrapper.getWrappedInstance();   Class<?> beanType = instanceWrapper.getWrappedClass();
//是否允許提前暴露物件,如果當前bean為單例,且允許迴圈引用,與當前bean正在建立中,則允許提前暴露 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { //將當前bean放入三級快取中 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
// Initialize the bean instance. Object exposedObject = bean; try { //開始設定屬性,當前bean依賴於其它bean,則需要 doGetBean 建立以來的bean,如果依賴的bean不存在,則首先建立依賴的bean,迴圈依賴發生的位置 populateBean(beanName, mbd, instanceWrapper); //執行初始化方法和aop增強,此時如果有aop,exposedObject就是增強以後的物件了,但是有一點需要注意,如果提前執行了aop,則exposedObject不會再次執行aop了 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { throw ex; }
if (earlySingletonExposure) { //這個我們可以看上面getSingleton方法,該方法的引數為false,就說明只允許去一級快取和二級快取獲取,此時bean在建立中,一級快取一定沒有,就看二級快取能不能獲取到了 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { //如果二級快取獲取到了,則說明提前執行了aop if (exposedObject == bean) { //exposedObject就是要放入單例池中的物件,如果提前執行了aop,則將exposedObject物件替換為aop以後的物件 //這個地方可能有一些疑問,exposedObject是原始物件執行過 依賴注入,而earlySingletonReference是提前執行aop的物件,沒有執行過依賴注入,是不是有什麼問題呢? //答案是不會,因為earlySingletonReference作為exposedObject的增強物件,內部是持有原物件的引用的 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 " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); } } } }
// Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); }
return exposedObject;}
//下面我們看一下 上面方法中的 將bean放入三級快取提前暴露的方法//addSingletonFactory(beanName, () -> 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 載入的整個生命週期,和三級快取的作用。

比如:

1、如何判斷是否存在迴圈依賴:使用 isSingletonCurrentlyInCreation(beanName) 這個方法

2、三級快取如何升級到二級快取的:參考第二步

3、提前執行 AOP:這個東西是在 獲取三級快取的時候執行的,裡面有一個 getEarlyBeanReference(beanName, mbd, bean) 這個 lambda 表示式,這個方法就是提前執行 aop,具體可以參考 AbstractAutoProxyCreator

4、如果提前執行 AOP,則需要替換原物件

文字總結 A、B 迴圈依賴

1、getBean(A) 先去單例池獲取,單例池不存在,二級快取獲取,二級快取不存在且允許提前訪問,三級快取中取,此時返回為空,開始載入 A

2、singletonsCurrentlyInCreation(A) 將 A 放入正在建立的 Map 中

3、new A(); 例項化 A

4、提前暴露 A,將 A 放入三級快取,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

5、設定屬性 populateBean(beanName, mbd, instanceWrapper);

6、發現 A 依賴 B,需要先建立 B

7、getBean(B)

8、先去單例池獲取 B,單例池不存在,二級快取獲取,二級快取不存在且允許提前訪問,三級快取中取,此時返回為空,開始載入 B

9、將 B 放入 singletonsCurrentlyInCreation() 的 Map 中

10、new B() 例項化 B

11、將 B 放入三級快取 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

12、設定屬性 populateBean(beanName, mbd, instanceWrapper);

13、發現 B 依賴 A

14、getBean(A)

15、發現三級快取中存在 A,getEarlyBeanReference(A, mbd, bean) 獲取 A,同時把 A 放入二級快取,刪除三級快取

16、執行 B 的 initializeBean 方法,執行 aop,獲取增強以後的引用

17、B 建立完了,將 B 放入單例池衝

18、繼續執行第 7 步,返回的 getBean(B)就是建立好的 B

19、接下來 A 初始化

20、因為 A 的三級快取中的 getEarlyBeanReference(beanName, mbd, bean) 被 B 已經執行過了

21、A 就能從二級快取中獲取自己的引用

22、如果發現引用變了,此時 A 就指向二級快取中的引用

23、將 A 放出單例池中

24、刪除二級快取和三級快取