意料之中、要求3-5年的Leader,最后選了應屆生
閑談
大家好,我是了不起,前段時間,了不起在當面試官,挑了許多人給leader去面談,最后可能是因為把之前某個想走的同事留了下來了,所以對新人沒有太多的要求,所以選了應屆生。
感覺如果是這種情況,還是比較利好應屆生的,不然有些業務比較特殊的活,需要有能力接下上一任的工作,對面試的人要求會非常的高,人也不好找,最后頭疼的也是我。
不提也罷,回歸正題,分享一道最近常用來面試1-2年工作經驗的人的面試題吧。
什么是Spring的循環依賴問題
圖片
在軟件開發的世界里,我們總是追求代碼的優雅與高效。目前Java主流的SpringBoot、SpringCloud框架無疑是我們最好的幫手。它不僅簡化了企業級應用的開發,還為我們提供了許多強大的功能。
比如依賴注入DI,但是,就像任何技術都有其雙刃劍的一面,依賴注入也不例外,Spring在進行依賴注入時最常見的一個問題——循環依賴。
舉例一個場景,我們有兩個Service類A和B,A類里有個a2方法需要調用了B類里的b1方法,B類里的b2方法需要用到A類里的a1方法。
圖片
那么按照我們Java代碼的無腦編程,就會是下面的這個情況:
ServiceA的a2方法調用ServiceB里的b1方法。
@Service
public class ServiceA implements Service {
@Autowired
private ServiceB serviceB;
@Override
public void a1() {
System.out.println("當前是ServiceA的a1方法"");
}
@Override
public void a2() {
System.out.println("這里將調用ServiceB的b1方法");
serviceB.b1();
}
}
同樣ServiceB的b2方法就調用ServiceA里的a1方法。
@Service
public class ServiceB implements Service{
@Autowired
private ServiceA serviceA;
@Override
public void b1() {
System.out.println("當前是ServiceB的b1方法");
}
@Override
public void b2() {
System.out.println("這里將調用ServiceA的a1方法");
serviceA.a1();
}
}
運行結果
當你運行這個SpringBoot應用的時候,會遇到一個錯誤,錯誤信息如下:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'serviceA': Requested bean is currently in creation: Is there an unresolvable circular reference?
拓展復習
還記得Bean的創建過程嗎?Spring 在啟動時會根據配置文件或注解來創建和初始化所有的bean。這個過程可以分為幾個階段:
- 實例化(Instantiation):Spring 容器創建一個bean的實例。
- 屬性填充(Population of Properties):Spring 容器設置bean的所有屬性,包括依賴注入。
- 初始化(Initialization):Spring 容器調用bean的初始化方法(如 @PostConstruct 注解的方法或 InitializingBean 接口的 afterPropertiesSet 方法)。
現在我們有兩個bean:ServiceA 和 ServiceB,它們相互依賴對方。具體來說:
- ServiceA 依賴 ServiceB
- ServiceB 依賴 ServiceA
當Spring嘗試創建 ServiceA 時,它會發現 ServiceA 需要 ServiceB。于是Spring開始創建 ServiceB。然而,在創建 ServiceB 的過程中,Spring 又發現 ServiceB 需要 ServiceA。這時,Spring 發現自己已經在一個創建 ServiceA 的過程中,從而導致了一個循環依賴。
圖片
好比這張圖一樣,把箭頭的方向可以理解成前提條件,是不是就一目了然了。彼此成為對方的前提條件。就好比,不考慮進化論,究竟是先雞還是先蛋?
回歸正題
在開發中,一般遇到這個問題,通常會使用@Lazy來解決。
@Service
public class ServiceB implements Service{
@Autowired
@Lazy
private ServiceA serviceA;
@Override
public void b1() {
System.out.println("當前是ServiceB的b1方法");
}
@Override
public void b2() {
System.out.println("這里將調用ServiceA的a1方法");
serviceA.a1();
}
}
它一方面可以減少Spring的IOC容器在啟動時的加載時間,一方面也可以解決Bean的循環依賴問題。
但是這是在日常開發使用的時候的處理方法,面試的時候肯定不會就這么放過你。
所以我們在面試的時候遇到這個問題,通常還會再多回答兩個方式。
Spring解決循環依賴必須是單例的Bean
這是一種依賴Spring提前暴露對象的方式來實現的。這種也叫半成品對象,通過對上面的學習,我們知道了循環依賴的原因是因為在創建的時候需要引用到另一個正在創建的對象,通過暴露這種半成品對象,讓初始化的時候能夠解決循環依賴的問題。
但是這種方式不能使用在原型對象的創建和初始化!背過面試題的都知道:
- 單例對象的特點:
單例對象在整個容器生命周期內只會被創建一次。
這種特性使得單例對象的依賴關系在容器啟動時就已經確定下來,不會發生變化。
- 原型對象的特點:
原型對象在每次請求時都會創建新的實例。
對于原型對象而言,每次創建新實例時都可能涉及到不同的對象實例,因此不能像單例那樣緩存并復用半成品對象。
不支持構造函數注入
Spring無法解決構造函數導致的循環依賴,是因為在對象實例化的過程中,構造函數都是最早被調用的,那個時候對象還沒完成實例化,所以沒辦法注入一個尚未完成創建的對象。
因此,解決循環依賴的一種方式,就是避開構造函數注入。
結論
上面的知識只是給你科普用的,不是讓你用來回答的。如果你實在不理解,那就背下面的吧!
- 重新設計,徹底消除循環依賴(是一句廢話沒錯,但是面試得講一下)
- 改成非構造器注入的形式,比如setter注入或者字段注入
- 使用@Lazy解決