Spring 是怎麼處理迴圈依賴的?

eaglelihh發表於2022-02-12

Java語法中的迴圈依賴

首先看一個使用建構函式的迴圈依賴,如下:

public class ObjectA {
    private ObjectB b;
    public ObjectA(ObjectB b) {
        this.b = b;
    }
}

public class ObjectB {
    private ObjectA a;

    public ObjectB(ObjectA a) {
        this.a = a;
    }
}

public class Main {
    public static void main(String[] args) {
        //ObjectB b = new ObjectB(new ObjectA(new ObjectB()));
    }
}

大家可以看上面這個例子,可以看出是沒有辦法new出ObjectA或者ObjectB的

那怎麼解決上面的例子呢?如下:

public class ObjectA {
    private ObjectB b;

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

    public ObjectB getB() {
        return this.b;
    }
}

public class ObjectB {
    private ObjectA a;

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

    public ObjectA getA() {
        return this.a;
    }
}

public class Main {
    public static void main(String[] args) {
        ObjectB b = new ObjectB();
        ObjectA a = new ObjectA();
        b.setA(a);
        a.setB(b);
        System.out.println(a + " " + a.getB());
        System.out.println(b + " " + b.getA());
    }
}

輸出如下:

cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460
cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460 cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d

可以看出把建構函式去掉,然後增加set方法就可以實現迴圈依賴的問題了。

Spring 的建構函式迴圈依賴

測試例子如下:

@Component
public class MyBeanOne {
    private MyBeanTwo myBeanTwo;

    @Autowired
    public MyBeanOne(MyBeanTwo myBeanTwo) {
        this.myBeanTwo = myBeanTwo;
    }
}

@Component
public class MyBeanTwo {
    private MyBeanOne myBeanOne;

    @Autowired
    public MyBeanTwo(MyBeanOne myBeanOne) {
        this.myBeanOne = myBeanOne;
    }
}

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(MyBeanOne.class, MyBeanTwo.class);
        MyBeanOne myBeanOne = context.getBean(MyBeanOne.class);
        MyBeanTwo myBeanTwo = context.getBean(MyBeanTwo.class);
        System.out.println(myBeanOne);
        System.out.println(myBeanTwo);
    }
}

輸出如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'myBeanOne': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)

DefaultSingletonBeanRegistry.beforeSingletonCreation()裡打斷點,看一下為什麼出錯了:
第一次經過:

第二次經過:

第三次經過:

我們可以猜測出錯的原因是這樣的:

去生成myBeanOne,需要生成myBeanTwo
去生成myBeanTwo,需要生成myBeanOne
去生成myBeanOne,發現myBeanOne已經在建立中了

Spring 的Set方式迴圈依賴

@Component
public class MyBeanOne {
    @Autowired
    @Getter
    private MyBeanTwo myBeanTwo;
}

@Component
public class MyBeanTwo {
    @Autowired
    @Getter
    private MyBeanOne myBeanOne;
}

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(MyBeanOne.class, MyBeanTwo.class);
        MyBeanOne myBeanOne = context.getBean(MyBeanOne.class);
        MyBeanTwo myBeanTwo = context.getBean(MyBeanTwo.class);
        System.out.println(myBeanOne + " " + myBeanOne.getMyBeanTwo());
        System.out.println(myBeanTwo + " " + myBeanTwo.getMyBeanOne());
    }
}

輸出:

cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114 cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869
cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869 cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114

可以看出通過set注入的方式是可以解決迴圈依賴問題的。

我們繼續在DefaultSingletonBeanRegistry.beforeSingletonCreation()裡打斷點,看一下set方式是怎麼經過這裡的:
發現只經過了兩次,沒有第三次,如果有第三次的話,也就拋異常了,所以只經過兩次是正常的。

為什麼建構函式有三次,而set方式有兩次?

我們看一下DefaultSingletonBeanRegistry.beforeSingletonCreation的呼叫鏈:

AbstractBeanFactory.doGetBean
DefaultSingletonBeanRegistry.getSingleton
DefaultSingletonBeanRegistry.beforeSingletonCreation

那我們就在AbstractBeanFactory.doGetBean這裡打斷點,看一下set方式,為什麼沒有第三次
第一次:

第二次:

第三次:

我們可以看到第三次的myBeanOne已經有值了,它就不會執行到DefaultSingletonBeanRegistry.beforeSingletonCreation
如果換成構造方式來除錯的話,在第三次,myBeanOne依舊是null的,就會繼續往下執行到DefaultSingletonBeanRegistry.beforeSingletonCreation,然後就會拋錯。

我們來看一下第三次的myBeanOne是怎麼獲取的:
DefaultSingletonBeanRegistry.getSingleton 方法如下:

可以看到是通過this.singletonFactories.get(beanName)得到一個工廠,通過這個工廠可以建立出對應的bean

我們再來看一下這些個工廠是什麼時候被放進去的,DefaultSingletonBeanRegistry.addSingletonFactory

為什麼通過建構函式注入的方式,沒有提前放入一個工廠

再執行AbstractAutowireCapableBeanFactory.createBeanInstance 方法時

set方式會執行以下:

呼叫這個方法之後,回退到 AbstractAutowireCapableBeanFactory.doCreateBean() 繼續往下執行,會把工廠放進去。

建構函式的方式會執行以下:

呼叫這個方法,後面會繼續獲取myBeanTwo,如下:

然後同理再獲取myBeanOne,就會拋異常了,它不會回退到 AbstractAutowireCapableBeanFactory.doCreateBean(),自然也不會把工廠放進去。

最後理一下

對於Set方式,當類構造好之後,會提前把生成這個類的工廠放到快取中;而建構函式的方式,由於存在建構函式,必須在當下去獲取依賴類,所以就沒辦法構造類,其實原理和剛開始舉的Java的例子是一個道理。

參考

Spring 解決迴圈依賴必須要三級快取嗎?
Spring迴圈依賴三級快取是否可以去掉第三級快取?
Spring 的迴圈依賴,原始碼詳細分析 → 真的非要三級快取嗎

相關文章