SpringBoot構(gòu)造器注入循環(huán)依賴及解決方案
SpringBoot構(gòu)造器注入循環(huán)依賴及解決
1. 循環(huán)依賴是什么?
Bean A 依賴 B,Bean B 依賴 A這種情況下出現(xiàn)循環(huán)依賴。
Bean A → Bean B → Bean A
更復(fù)雜的間接依賴造成的循環(huán)依賴如下。
Bean A → Bean B → Bean C → Bean D → Bean E → Bean A
(解釋一下,三個以上其實和兩個是一樣的,多加幾個@lazy就可以了,等到后面bean創(chuàng)建成功再創(chuàng)建)
2. 循環(huán)依賴會產(chǎn)生什么結(jié)果?
當(dāng)Spring正在加載所有Bean時,Spring嘗試以能正常創(chuàng)建Bean的順序去創(chuàng)建Bean。
例如,有如下依賴:
Bean A → Bean B → Bean C
Spring先創(chuàng)建beanC,接著創(chuàng)建bean B(將C注入B中),最后創(chuàng)建bean A(將B注入A中)。
但當(dāng)存在循環(huán)依賴時,Spring將無法決定先創(chuàng)建哪個bean。這種情況下,Spring將產(chǎn)生異常BeanCurrentlyInCreationException。
當(dāng)使用構(gòu)造器注入時經(jīng)常會發(fā)生循環(huán)依賴問題。如果使用其它類型的注入方式能夠避免這種問題。
簡單構(gòu)造器注入循環(huán)依賴實例
項目結(jié)構(gòu)
如下:

1.首先定義兩個相互通過構(gòu)造器注入依賴的bean。
/**
* @Author: lixs
* @Date: 2021/4/6
* @Description: 循環(huán)依賴類A
*/
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(@Lazy CircularDependencyB circB) {
this.circB = circB;
}
}
/**
1. @Author: lixs
2. @Date: 2021/4/6
3. @Description: 循環(huán)依賴類B
*/
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
@Autowired
public CircularDependencyB(@Lazy CircularDependencyA circA) {
this.circA= circA;
}
}
2.創(chuàng)建配置類
/**
1. @Author: lixs
2. @Date: 2021/4/6
3. @Description: 配置類
*/
@Configuration
@ComponentScan(basePackages = { "com.li.springboot.bean" })
public class TestConfig {
}
3.創(chuàng)建測試類
@SpringBootTest
@ContextConfiguration(classes = {TestConfig.class})
class ApplicationTests {
@Test
public void givenCircularDependency_whenConstructorInjection_thenItFails() {
// Empty test; we just want the context to load
}
}
正常運行結(jié)果:

上面就是報的是循環(huán) 依賴無法創(chuàng)建bean錯誤!
解決方案
1.重新設(shè)計
重新設(shè)計結(jié)構(gòu),不用這種方式去創(chuàng)建bean。
2.使用注解 @Lazy
一種最簡單的消除循環(huán)依賴的方式是通過延遲加載。
在注入依賴時,先注入代理對象,當(dāng)首次使用時再創(chuàng)建對象完成注入。
/**
* @Author: lixs
* @Date: 2021/4/6
* @Description: 循環(huán)依賴類A
*/
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(@Lazy CircularDependencyB circB) {
this.circB = circB;
}
}
使用@Lazy后,運行代碼,可以看到異常消除。

3.使用Setter/Field注入
Spring文檔建議的一種方式是使用setter注入。當(dāng)依賴最終被使用時才進(jìn)行注入。對前文的樣例代碼少做修改,來觀察測試效果。
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public void setCircB(CircularDependencyB circB) {
this.circB = circB;
}
public CircularDependencyB getCircB() {
return circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
測試類
@SpringBootTest
@ContextConfiguration(classes = {TestConfig.class})
class ApplicationTests {
@Autowired
ApplicationContext context; //spring容器對象
@Bean
public CircularDependencyA getCircularDependencyA() {
return new CircularDependencyA();
}
@Bean
public CircularDependencyB getCircularDependencyB() {
return new CircularDependencyB();
}
@Test
public void testCircularDependency() {
//拿到bean對象
CircularDependencyA circA = context.getBean(CircularDependencyA.class);
//獲取屬性值
String result=circA.getCircB().getMessage();
System.out.println(result);
}
4.使用@PostConstruct
@PostContruct是spring框架的注解,在方法上加該注解會在項目啟動的時候執(zhí)行該方法,也可以理解為在spring容器初始化的時候執(zhí)行該方法。
@Component
public class CircularDependencyA {
@Autowired
private CircularDependencyB circB;
@PostConstruct
public void init() {
circB.setCircA(this);
}
public CircularDependencyB getCircB() {
return circB;
}
}
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
5.實現(xiàn)ApplicationContextAware與InitializingBean
(1)如果某個類實現(xiàn)了ApplicationContextAware接口,會在類初始化完成后調(diào)用setApplicationContext()方法進(jìn)行操作
(2)如果某個類實現(xiàn)了InitializingBean接口,會在類初始化完成后,并在setApplicationContext()方法執(zhí)行完畢后,調(diào)用afterPropertiesSet()方法進(jìn)行操作
@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
private CircularDependencyB circB;
private ApplicationContext context;
public CircularDependencyB getCircB() {
return circB;
}
@Override
public void afterPropertiesSet() throws Exception {
circB = context.getBean(CircularDependencyB.class);
}
@Override
public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
context = ctx;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
總結(jié)
處理循環(huán)依賴有多種方式。
首先考慮是否能夠通過重新設(shè)計依賴來避免循環(huán)依賴。
如果確實需要循環(huán)依賴,那么可以通過前文提到的方式來處理。
優(yōu)先建議使用setter注入來解決。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
從零開始使用IDEA創(chuàng)建SpringBoot項目(圖文)
這篇文章主要介紹了從零開始使用IDEA創(chuàng)建SpringBoot項目(圖文),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
mybatis的動態(tài)sql之if test的使用說明
這篇文章主要介紹了mybatis的動態(tài)sql之if test的使用說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Java 編程如何使用 Class.forName() 加載類
在一些應(yīng)用中,無法事先知道使用者將加載什么類,而必須讓使用者指定類名稱以加載類,可以使用 Class的靜態(tài)forName()方法實現(xiàn)動態(tài)加載類,這篇文章主要介紹了Java編程如何使用Class.forName()加載類,需要的朋友可以參考下2022-06-06
SpringMVC @RequestMapping注解作用詳解
通過@RequestMapping注解可以定義不同的處理器映射規(guī)則,下面這篇文章主要給大家介紹了關(guān)于SpringMVC中@RequestMapping注解用法的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01
SpringBoot靜態(tài)視頻實時播放的實現(xiàn)代碼
這篇文章主要介紹了SpringBoot靜態(tài)視頻實時播放的實現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
詳解將Eclipse代碼導(dǎo)入到AndroidStudio的兩種方式
本篇文章主要介紹了詳解將Eclipse代碼導(dǎo)入到AndroidStudio的兩種方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12
Spring?AI?+?ollama?本地搭建聊天?AI?功能
這篇文章主要介紹了Spring?AI?+?ollama?本地搭建聊天?AI?,本文通過實例圖文相結(jié)合給大家講解的非常詳細(xì),需要的朋友可以參考下2024-11-11

