在我們的開發中,會不可避免的遇到Bean之間循環依賴的,所謂循環依賴,就是兩個或者兩個以上的Bean互相持有對方,這樣在程序運行調用中,會出現這種循環依賴的現象,假設是兩個Bean,當程序調用Bean A時,Bean A中依賴Bean B,在Bean A中調用Bean B時,Bean B中又依賴了Bean A,這樣就形成了循環依賴,如下圖:
先從一個小例子來說明,使用Spring框架如果出現循環依賴,會正常運行嗎?下例是在Spring Boot的基礎上構建的。
代碼結構如下:
程序訪問入口是HelloController,它里面調用了HelloService1:
package com.pig.employee.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMApping; import org.springframework.web.bind.annotation.RestController; import com.pig.employee.service1.HelloService1; @RestController public class HelloController { @Autowired HelloService1 helloService1; @RequestMapping("/hello") public String sayHello() { return helloService1.say1(); } }
看一下HelloService1對應的實現類:
package com.pig.employee.service1.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.pig.employee.service1.HelloService1; import com.pig.employee.service2.HelloService2; @Service("helloService1") public class HelloService1Impl implements HelloService1 { @Autowired private HelloService2 helloService2; @Override public String say1() { System.out.println(helloService2.toString()); return helloService2.say2(); } }
實現類中依賴了HelloService2,再來看一下HelloService2的實現類:
package com.pig.employee.service2.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.pig.employee.service1.HelloService1; import com.pig.employee.service2.HelloService2; @Service("helloService2") public class HelloService2Impl implements HelloService2 { @Autowired private HelloService1 helloService1; @Override public String say2() { System.out.println(helloService1.toString()); return "helloService2 say hello"; } }
HelloService2的實現類中又依賴了HelloService1,這樣就形成了循環依賴,依托于Spring框架,這樣的循環依賴能運行成功嗎?廢話不多說,直接運行不就出答案了,啟動EmployeeApplication:
啟動沒有問題,再來訪問一下,瀏覽器輸入:http://localhost:8080/hello
成功訪問,再來看一下控制臺,兩個Bean也都已經實例化:
上面的簡單例子可以說明Spring框架可以解決Bean之間循環依賴的,下面就來探究Spring是怎么做到的?
對于Spring中Bean的管理,下圖一目了然:
先調用構造函數進行實例化,然后填充屬性,再接著進行其他附加操作和初始化,正是這樣的生命周期,才有了Spring的解決循環依賴,這樣的解決機制是根據Spring框架內定義的三級緩存來實現的,也就是說:三級緩存解決了Bean之間的循環依賴。我們從源碼中來說明。
先來看Spring中Bean工廠是怎么獲取Bean的(AbstractBeanFactory中):
一級一級向下尋找,找出了前面提到的三級緩存,也就是三個Map集合類:
singletonObjects:第一級緩存,里面放置的是實例化好的單例對象;
earlySingletonObjects:第二級緩存,里面存放的是提前曝光的單例對象;
singletonFactories:第三級緩存,里面存放的是要被實例化的對象的對象工廠。
所以當一個Bean調用構造函數進行實例化后,即使屬性還未填充,就可以通過三級緩存向外暴露依賴的引用值(所以循環依賴問題的解決也是基于JAVA的引用傳遞),這也說明了另外一點,基于構造函數的注入,如果有循環依賴,Spring是不能夠解決的。還要說明一點,Spring默認的Bean Scope是單例的,而三級緩存中都包含singleton,可見是對于單例Bean之間的循環依賴的解決,Spring是通過三級緩存來實現的。源碼是讓我們知其然并且知其所以然的最好參考,所以多多閱讀源碼!