1. 程式人生 > 程式設計 >spring解決迴圈依賴的簡單方法

spring解決迴圈依賴的簡單方法

Spring內部如何解決迴圈依賴,一定是單預設的單例Bean中,屬性互相引用的場景。比如幾個Bean之間的互相引用:

spring解決迴圈依賴的簡單方法

或者

spring解決迴圈依賴的簡單方法

setter方式原型,prototype

原型(Prototype)的場景是不支援迴圈依賴的,因為“prototype”作用域的Bean,為每一個bean請求提供一個例項,Spring容器不進行快取,因此無法提前暴露一個建立中的Bean,會丟擲異常。

構造器引數迴圈依賴

Spring容器會將每一個正在建立的Bean 識別符號放在一個“當前建立Bean池”中,Bean識別符號在建立過程中將一直保持在這個池中。

因此如果在建立Bean過程中發現自己已經在“當前建立Bean池”裡時將丟擲BeanCurrentlyInCreationException異常表示迴圈依賴;而對於建立完畢的Bean將從“當前建立Bean池”中清除掉。

Spring容器先建立單例A,A依賴B,然後將A放在“當前建立Bean池”中,此時建立B,B依賴C,然後將B放在“當前建立Bean池”中,此時建立C,C又依賴A, 但是,此時A已經在池中,所以會報錯,,因為在池中的Bean都是未初始化完的,所以會依賴錯誤 ,(初始化完的Bean會從池中移除)。

setter方式單例,預設方式

那麼預設單例的屬性注入場景,Spring是如何支援迴圈依賴的?

既然,要解決迴圈依賴,那肯定存在著依賴,我們假設有兩個類:

A和B,A->B,B->A,且二者是通過@Autowired相互注入的。

我們假設從A開始建立,那就是先建立A物件,然後建立B物件,再通過反射把B物件set到A物件的屬性上去(fieldB.set(a,b)),在建立B的時候發現它又依賴於A,這時候同樣地,我要尋找一個A物件set到B的屬性上去,但是我們的系統中只能存在一個A物件(單例)。

為了解決這個問題,需要再前面建立A物件的時候就把它儲存起來?我們假設儲存在快取中,後面B物件要使用的時候先去快取中查詢一下不就OK了嘛?!

所以,解決迴圈依賴的方法就是儲存所有建立的物件,後面建立物件的時候有依賴的情況先去快取中找一下,找到了直接set到那個正在建立的物件的屬性上,沒找到就建立一個新的物件給那個正在建立的物件,並儲存到快取中。

實際上,Spring中也是這麼幹的,只不過它的快取不只一個,而是有四個。

具體可以看下這邊文章。

https://www.jb51.net/article/195897.htm

Spring解決迴圈依賴

首先,Spring內部維護了三個Map,也就是我們通常說的三級快取。

在Spring的DefaultSingletonBeanRegistry類中,類上方掛著這三個Map:

  • singletonObjects 它是我們最熟悉的朋友,俗稱“單例池”“容器”,快取建立完成單例Bean的地方。
  • singletonFactories 對映建立Bean的原始工廠
  • earlySingletonObjects 對映Bean的早期引用,也就是說在這個Map裡的Bean不是完整的,甚至還不能稱之為“Bean”,只是一個Instance.

後兩個Map其實是“墊腳石”級別的,只是建立Bean的時候,用來藉助了一下,建立完成就清掉了。

為什麼成為後兩個Map為墊腳石,假設最終放在singletonObjects的Bean是你想要的一杯“涼白開”。

那麼Spring準備了兩個杯子,即singletonFactories和earlySingletonObjects來回“倒騰”幾番,把熱水晾成“涼白開”放到singletonObjects中

迴圈依賴的本質

定義兩個類A與B:

public class A {
 private B b;

 public B getB() {
  return b;
 }

 public void setB(B b) {
  this.b = b;
 }

}
public class B {
 public A getA() {
  return a;
 }

 public void setA(A a) {
  this.a = a;
 }

 private A a;
}
public class CircularDependency {
 private static Map<String,Object> cacheMap = new HashMap<>(2);

 public static void main(String[] args) throws Exception {
  // 假裝掃描出來的物件
  Class[] classes = { A.class,B.class };
  // 假裝專案初始化例項化所有bean
  for (Class aClass : classes) {
   getBean(aClass);
  }
  // check
  System.out.println(getBean(B.class).getA() == getBean(A.class));
  System.out.println(getBean(A.class).getB() == getBean(B.class));
 }
 
 private static <T> T getBean(Class<T> beanClass) throws Exception{
  // 本文用類名小寫 簡單代替bean的命名規則
  String beanName = beanClass.getSimpleName().toLowerCase();
  // 如果已經是一個bean,則直接返回
  if (cacheMap.containsKey(beanName)) {
   return (T) cacheMap.get(beanName);
  }
  // 將物件本身例項化
  Object object = beanClass.getDeclaredConstructor().newInstance();
  // 放入快取
  cacheMap.put(beanName,object);
  // 把所有欄位當成需要注入的bean,建立並注入到當前bean中
  Field[] fields = object.getClass().getDeclaredFields();
  for (Field field : fields) {
   field.setAccessible(true);
   // 獲取需要注入欄位的class
   Class<?> fieldClass = field.getType();
   String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
   // 如果需要注入的bean,已經在快取Map中,那麼把快取Map中的值注入到該field即可
   // 如果快取沒有 繼續建立
   field.set(object,cacheMap.containsKey(fieldBeanName) ? cacheMap.get(fieldBeanName) : getBean(fieldClass));
  }
  // 屬性填充完成,返回
  return (T) object;
 }
}

這段程式碼的效果,其實就是處理了迴圈依賴,並且處理完成後,cacheMap中放的就是完整的“Bean”了。

總結

到此這篇關於spring解決迴圈依賴的文章就介紹到這了,更多相關spring解決迴圈依賴內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!