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 的迴圈依賴,原始碼詳細分析 → 真的非要三級快取嗎