Spring如何解決循環依賴


作者:Grey

原文地址:Spring如何解決循環依賴

如果X這個類依賴了Y,Y這個類依賴了X,就產生了循環依賴。在普通Java(非Spring框架)下,這並不是一個問題。

參考如下示例代碼:

public class Demo {
    public static void main(String[] args) {
        X a = new X();
        Y b = new Y();
        a.y = b;
        b.x = a;
        System.out.println(a);
        System.out.println(b);
    }
}

class X {
    Y y;
}

class Y {
    X x;
}

但是Spring創建對象由於有相對復雜的生命周期,所以可能會導致循環依賴的問題,我們將如上代碼轉換成使用Spring的方式:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

public class CircularDependenciesDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 將當前類配置為Configuration類
        applicationContext.register(CircularDependenciesDemo.class);
        // 是否支持循環依賴
        // applicationContext.setAllowCircularReferences(true);
        // 啟動
        applicationContext.refresh();
        System.out.println(applicationContext.getBean("x"));
        System.out.println(applicationContext.getBean("y"));
        // 關閉上下文
        applicationContext.close();
    }

    @Bean
    public static X x() {
        return new X();
    }

    @Bean
    public static Y y() {
        return new Y();
    }
}

class X {
    @Autowired
    Y y;
}

class Y {
    @Autowired
    X x;
}

運行,正常打印出:

git.snippets.fail1.X@ed9d034
git.snippets.fail1.Y@6121c9d6

以下是循環依賴是否支持的開關,如果設置為true,則支持循環依賴。

// 是否支持循環依賴
applicationContext.setAllowCircularReferences(true);

如果設置為false,再次運行main方法,則報錯,以下為簡略報錯信息:

nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:Error creating bean with name'x':
        Requested bean is currently in creation:Is there an unresolvable circular reference?

通過上述實驗,可以了解,Spring解決了循環依賴的問題,如何解決的呢?我們需要先看下Spring中Bean的生命周期:

以x的創建為例:

X階段:

  1. 解析XML或者注解,將信息注冊到BeanDefinition中
  2. 對象的實例化,可以理解成 X x = new X();
  3. x的屬性填充(這里就涉及到要填充Y的實例)
  4. x的初始化
  5. Bean后置處理器進行處理(比如AOP)
  6. 把Bean添加到單例池中

在進行到第3步的時候,Spring會從單例池中找Y對應的Bean對象,如果找不到,則會執行y對象的創建過程生命周期,y也會經歷和x一樣的生命周期:

Y階段:

  1. 解析XML或者注解,將信息注冊到BeanDefinition中
  2. 對象的實例化,可以理解成 Y y = new Y();
  3. y的屬性填充(這里就涉及到要填充x的實例)
  4. y的初始化
  5. Bean后置處理器進行處理(比如AOP)
  6. 把Bean添加到單例池中

這里執行到第三步的時候,需要找到x,但是x還沒有進入單例池,所以,X階段卡在第三步無法繼續執行,Y階段也卡在第三步無法繼續執行,這就導致了循環依賴問題。

如何解決這個問題?

可以使用一個map,假設叫Tmap,在X階段第二步的時候,將new出來的x放入這個Tmap中,這樣一來,Y階段的第3步涉及x的實例填充,就可以這樣執行:

先從單例池中找x,找不到,再從map中找x,此時因為X階段的第2步已經把new出來的x放入到Tmap中,所以可以從Tmap中找到,填充即可。 這樣,Y階段可以順利執行完畢,然后X階段正常結束

這樣就解決了上面提到的循環依賴的問題。

但是會帶來一個新的問題,我們用的這個Tmap存放的是X的原始對象,但是,有一種非常常見的情況是,X階段的第5步,如果做了AOP,此時,會生成一個代理對象,假設叫XProxy,

那么我們需要放入Tmap中的其實是這個XProxy,而且填充Y中x屬性也是用的XProxy對象,而非X對應的原始對象,所以剛剛我們使用Tmap這個方案就導致了新的問題:

在X階段的第5步中,如果存在AOP,那么Y階段的第3步處理的時候,如何正確的把X的代理對象填充到Y中?

如何解決這個問題呢?

可以考慮提前進行AOP,即:

在X階段的第2步中,在new出X以后,提前進行AOP,生成代理對象,並且把代理對象放入Tmap中,這樣,后續填充的時候,從Tmap中拿出的就是代理對象, 問題就解決了。

但是,新的問題又來了,正常的Bean的生命周期到第5步才進行AOP,怎么判斷一個Bean需要提前AOP呢? 由上面的推論可知:只有出現了循環依賴且后置處理器中配置了AOP,才需要進行提前AOP,否則,不需要提前進行AOP,簡言之:如果沒有出現循環依賴,就不需要提前AOP

那么新的問題又來了,

問題1: 如何判斷出現了循環依賴呢?

問題2: 如果已經進行了提前AOP,那么執行到第5步的時候,怎么判斷已經執行過了AOP?

先看問題2,Spring中處理Aop的類是AbstractAutoProxyCreator,其中定義了一個Map(earlyProxyReferences)來存已經提前進行了Aop的Bean, 關鍵代碼如下:

 @Override
 public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    return wrapIfNecessary(bean, beanName, cacheKey);
   }
  }
  return bean;
 }

回到第一個問題:如何判斷出現了循環依賴呢?

我們可以在X階段第2步中加入一個map,在X創建的時候,把x加入一個map,這個map表示正在創建的Bean。

在Y階段的時候,判斷需要X,先去單例池中找,找不到,然后去這個存正在創建bean的map中找X,發現X找到,說明出現了循環依賴,所以需要提前AOP

但是,提前AOP的前提,是需要一個原始對象的,這個原始對象存在哪里? 這就是第三級緩存,即一個Map,其中Key是beanName, Value存的是lambda表達式

在提前AOP的時候,從三級緩存中拿到這個lambda表達式,執行,就擁有了原始對象(aop)

注意:需要提前aop的情況下,lambda表達式得到的是代理對象,不需要提前aop的情況下,得到的就是原始對象。

解決了如上問題,又會引入一個新問題,就是:如果新出現一個Bean,假設叫Z,也依賴X,且我們假設X已經配置了AOP,Z類的代碼如下:

class Z {
    @Autowired
    X x;
}

執行Z階段和執行Y階段應該是類似的,在第2步判斷的時候,都會判斷需要進行提前AOP,且會通過三級緩存生成一個X的代理對象,此時Z階段執行和Y階段執行都會走這步,這樣就導致了會生成兩個X的代理對象,

如何保證只有一個代理對象呢? 那么二級緩存登場了,三級緩存生成的代理對象放入這個二級緩存中即可,下次從二級緩存中取出對應代理對象即可。

那么上述的代理對象什么時候放入單例池中呢?

Spring中是這樣處理的,Bean先從單例池找,沒有找到,二級緩存中找,找到了代理對象,就把代理對象放入單例池,刪掉二級緩存中的代理對象即可。

最后,總的流程就是:

先從單例池找,找不到,去二級緩存找,再去三級緩存中找,三級緩存找到,會把對應記錄刪除,並把代理對象(如果有AOP的話)或者原始對象放入二級緩存。

二級緩存除了提高效率以外,最重要的作用是防止生成兩個aop對象

正常情況下,二級三級緩存都沒有用,二級三級緩存主要是為了解決循環依賴的問題。

參考資料

終於有人把Spring的循環依賴問題解釋清楚了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM