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
(解釋一下,三個(gè)以上其實(shí)和兩個(gè)是一樣的,多加幾個(gè)@lazy就可以了,等到后面bean創(chuàng)建成功再創(chuàng)建)
2. 循環(huán)依賴會(huì)產(chǎn)生什么結(jié)果?
當(dāng)Spring正在加載所有Bean時(shí),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)依賴時(shí),Spring將無法決定先創(chuàng)建哪個(gè)bean。這種情況下,Spring將產(chǎn)生異常BeanCurrentlyInCreationException。
當(dāng)使用構(gòu)造器注入時(shí)經(jīng)常會(huì)發(fā)生循環(huán)依賴問題。如果使用其它類型的注入方式能夠避免這種問題。
簡(jiǎn)單構(gòu)造器注入循環(huán)依賴實(shí)例
項(xiàng)目結(jié)構(gòu)
如下:
1.首先定義兩個(gè)相互通過構(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)建測(cè)試類
@SpringBootTest @ContextConfiguration(classes = {TestConfig.class}) class ApplicationTests { @Test public void givenCircularDependency_whenConstructorInjection_thenItFails() { // Empty test; we just want the context to load } }
正常運(yùn)行結(jié)果:
上面就是報(bào)的是循環(huán) 依賴無法創(chuàng)建bean錯(cuò)誤!
解決方案
1.重新設(shè)計(jì)
重新設(shè)計(jì)結(jié)構(gòu),不用這種方式去創(chuàng)建bean。
2.使用注解 @Lazy
一種最簡(jiǎn)單的消除循環(huán)依賴的方式是通過延遲加載。
在注入依賴時(shí),先注入代理對(duì)象,當(dāng)首次使用時(shí)再創(chuàng)建對(duì)象完成注入。
/** * @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后,運(yùn)行代碼,可以看到異常消除。
3.使用Setter/Field注入
Spring文檔建議的一種方式是使用setter注入。當(dāng)依賴最終被使用時(shí)才進(jìn)行注入。對(duì)前文的樣例代碼少做修改,來觀察測(cè)試效果。
@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; } }
測(cè)試類
@SpringBootTest @ContextConfiguration(classes = {TestConfig.class}) class ApplicationTests { @Autowired ApplicationContext context; //spring容器對(duì)象 @Bean public CircularDependencyA getCircularDependencyA() { return new CircularDependencyA(); } @Bean public CircularDependencyB getCircularDependencyB() { return new CircularDependencyB(); } @Test public void testCircularDependency() { //拿到bean對(duì)象 CircularDependencyA circA = context.getBean(CircularDependencyA.class); //獲取屬性值 String result=circA.getCircB().getMessage(); System.out.println(result); }
4.使用@PostConstruct
@PostContruct是spring框架的注解,在方法上加該注解會(huì)在項(xiàng)目啟動(dòng)的時(shí)候執(zhí)行該方法,也可以理解為在spring容器初始化的時(shí)候執(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.實(shí)現(xiàn)ApplicationContextAware與InitializingBean
(1)如果某個(gè)類實(shí)現(xiàn)了ApplicationContextAware接口,會(huì)在類初始化完成后調(diào)用setApplicationContext()方法進(jìn)行操作
(2)如果某個(gè)類實(shí)現(xiàn)了InitializingBean接口,會(huì)在類初始化完成后,并在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è)計(jì)依賴來避免循環(huán)依賴。
如果確實(shí)需要循環(huán)依賴,那么可以通過前文提到的方式來處理。
優(yōu)先建議使用setter注入來解決。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用java生成json時(shí)產(chǎn)生棧溢出錯(cuò)誤問題及解決方案
這篇文章主要介紹了使用java生成json時(shí)產(chǎn)生棧溢出錯(cuò)誤問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06SpringBoot從2.7.x 升級(jí)到3.3注意事項(xiàng)
從SpringBoot 2.7.x升級(jí)到3.3涉及多個(gè)重要變更,特別是因?yàn)?nbsp;Spring Boot 3.x 系列基于 Jakarta EE 9,而不再使用 Java EE,本文就來詳細(xì)的介紹一下,感興趣的可以了解一下2024-09-09Spring?boot?啟動(dòng)流程及外部化配置方法
平時(shí)我們開發(fā)Spring boot 項(xiàng)目的時(shí)候,一個(gè)SpringBootApplication注解加一個(gè)main方法就可以啟動(dòng)服務(wù)器運(yùn)行起來,那它到底是怎么運(yùn)行起來的呢?這篇文章主要介紹了Spring?boot?啟動(dòng)流程及外部化配置,需要的朋友可以參考下2022-12-12三種簡(jiǎn)單排序算法(使用java實(shí)現(xiàn))
下面小編就為大家?guī)硪黄N簡(jiǎn)單排序算法(使用java實(shí)現(xiàn))。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-07-07Java springboot 整合 Nacos的實(shí)例代碼
這篇文章主要介紹了Java springboot 整合 Nacos的實(shí)例,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04