Spring內部如何解決迴圈依賴,一定是單預設的單例Bean中,屬性互相引用的場景。比如幾個Bean之間的互相引用:
或者
原型(Prototype)的場景是不支援迴圈依賴的,丟擲異常。
基於構造器的迴圈依賴,是不存在的。
那麼預設單例的屬性注入場景,Spring是如何支援迴圈依賴的?
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”了