深談Spring如何解決Bean的迴圈依賴

可愛的小鋒發表於2023-04-17

1. 什麼是迴圈依賴

Java迴圈依賴指的是兩個或多個類之間的相互依賴,形成了一個迴圈的依賴關係,這會導致程式編譯失敗或執行時出現異常。下面小嶽就帶大家來詳細分析下Java迴圈依賴。

簡單來講就是:假設有兩個人是:A和B,A想要向B借錢,但B需要先向A借錢。這種情況就形成了迴圈依賴關係,無法解決借錢的問題。

接下來小嶽用一個程式碼案例再來跟大傢俱體講講什麼是迴圈依賴:

假設有兩個Java類:A和B,A類依賴於B類,而B類又依賴於A類,程式碼如下:

// A.java
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}

// B.java
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}

在這個例子中,類A和類B之間形成了迴圈依賴關係,因為它們互相依賴對方。 如果我們在執行時建立一個A物件和一個B物件,並且嘗試將它們互相設定為對方的屬性,程式就會陷入無限迴圈中。

其實Java迴圈依賴是一個非常常見的問題,因為當兩個類之間相互依賴時,就可能出現這種情況。

解決這個問題的一種方法是透過重構程式碼來消除迴圈依賴關係,使得類之間的依賴關係變得更清晰。另一種方法是使用依賴注入框架,如Spring,它可以自動處理依賴關係並避免迴圈依賴問題。 無論使用哪種方法,消除迴圈依賴關係都是很重要的,以確保程式的正確性和穩定性。

現在大家知道什麼迴圈依賴了吧,在理解這個概念之後,我們回到正題:為什麼被Spring容器管理的Bean物件會出現迴圈依賴問題呢? 請大家繼續往下看吧

2. Spring Bean的迴圈依賴問題

大家都清楚,被Spring容器管理的物件叫做Bean,那麼為什麼這個Bean物件會出現迴圈依賴的問題呢?

目前我們想要理解Bean的迴圈依賴問題,首先我們需要將這個被Spring容器管理的物件Bean給吃透了,這樣對我們後續的理解會有很大幫助的,所以接下來就跟小嶽一起來瞭解Bean的建立過程吧!

2.1 Bean的建立過程

在計算機程式設計中,“Bean”通常是指一個Java物件,它包含一些屬性和對這些屬性進行操作的方法。
Bean物件通常用於傳遞資料和在不同的元件之間共享資料。下面是建立一個Java Bean的基本步驟:

● 建立一個Java類:建立一個Java類並給它一個有意義的名稱,例如,“PersonBean”或“EmployeeBean”。這個類應該具有一些屬性和方法,用於獲取和設定這些屬性。

● 定義屬性:在類中定義屬性,例如,如果建立一個“PersonBean”,則應該定義“name”、“age”、“address”等屬性。每個屬性應該有一個資料型別和一個可選的初始值。

● 建立getter和setter方法:為每個屬性建立一個getter方法和一個setter方法。getter方法用於獲取屬性的值,setter方法用於設定屬性的值。

● 實現Serializable介面:如果需要將Bean物件序列化或將其儲存在會話或應用程式範圍內,則應實現Serializable介面。

● 新增建構函式:可以新增建構函式來初始化Bean物件的屬性。

● 新增其他方法:可以新增其他方法來執行與Bean物件相關的其他操作。

概念性的知識點看起來確實會有點枯燥哈,接下來我們上程式碼,透過程式碼案例來加深下大家的印象:

假設有一個人叫做 Mr. Bean,他想要建立一個 Java Bean。他首先需要定義一個類來表示這個 Bean,我們給這個類取個名字叫做MyBean。下面是這個類的程式碼:

public class MyBean {
private String name;
private int age;

public MyBean(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

上面的程式碼定義了一個具有nameage屬性的類MyBean。這個類有一個建構函式,可以用來建立物件,並且有一些gettersetter方法來訪問和修改這些屬性。

現在,Mr. Bean想要建立一個MyBean物件。他需要先建立一個MyBean類的例項,然後透過呼叫 setter方法來設定屬性的值。下面是他的程式碼。

MyBean myBean = new MyBean("Mr. Bean", 30);
myBean.setName("Rowan Atkinson");
myBean.setAge(66);

上面的程式碼首先建立了一個MyBean類的例項,名字為"Mr. Bean",年齡為 30。然後,它透過呼叫setNamesetAge方法來修改物件的屬性。現在,這個物件的名字為 "Rowan Atkinson",年齡為 66。

最後,Mr. Bean可以在程式的其他地方使用這個MyBean物件,例如將它傳遞給其他方法或儲存在一個集合中。

簡單的總結下:其實建立一個 Java Bean 就像給一個人起名字和年齡一樣簡單。只需要定義一個類,給它新增一些屬性和方法,然後使用它建立物件即可,是不是很簡單~

好啦,清楚了Bean物件之後,我們就來看看為什麼Spring Bean會產生迴圈依賴的問題?

2.2 為什麼Spring Bean會產生迴圈依賴問題?

其實我們透過上面對於迴圈依賴以及Bean物件的一個初步認識,大家需要認識到一個問題,那就是:迴圈依賴問題是指在Spring容器中,兩個或多個Bean互相依賴對方,導致無法成功建立Bean例項的情況。這種問題通常是由於Bean之間的依賴關係複雜或者設計不合理引起的。

假設有兩個人,一個名叫Tom,一個名叫Jerry,他們非常喜歡一起玩。Tom想要拿到Jerry手中的玩具,而Jerry也想要拿到Tom手中的糖果。於是,他們決定互相交換,但是交換的條件是必須同時完成。Tom不會放開玩具,除非他拿到了糖果;而Jerry也不會放開糖果,除非他拿到了玩具。他們一直在互相等待,最終誰也沒有得到自己想要的東西。

在Java程式碼中,迴圈依賴的問題通常是由於Bean之間的建構函式或者setter方法相互依賴造成的。例如,假設我們有一個名為UserService的Bean和一個名為UserRepository的Bean,UserService需要UserRepository來進行資料庫操作,而UserRepository需要UserService來進行使用者許可權驗證。這時,如果我們使用建構函式注入,程式碼可能會像這樣:

public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}

public class UserRepository {
private final UserService userService;
public UserRepository(UserService userService) {
this.userService = userService;
}
}

這段程式碼就存在迴圈依賴問題,因為UserService依賴UserRepository,而UserRepository又依賴UserService,兩者之間形成了一個環。

為瞭解決這個問題,Spring提供了幾種解決方案,包括使用setter注入、使用@Autowired註解、使用@Lazy註解、使用@DependsOn註解等。這些解決方案可以打破Bean之間的迴圈依賴,確保Bean能夠被成功建立。

總的來說,迴圈依賴問題是Spring容器中常見的問題之一,但是使用正確的解決方案,我們可以輕鬆地避免這個問題的出現。同時,也希望大家能夠像Tom和Jerry一樣,互相理解,積極協作,讓軟體開發變得更加愉快!

2.3 迴圈依賴問題

2.3.1 Spring不能解決的迴圈依賴問題

在這裡小嶽跟大家講一個要點,一定要記住哦!

其實,Spring 並不能解決所有迴圈依賴的問題哦,接下來就帶大家看看什麼情況下的迴圈依賴是不可以被解決的吧。

為瞭解釋 Spring 不能解決所有迴圈依賴問題,小嶽給大家講一個小故事。

有一次,程式設計師 A 和程式設計師 B 在討論迴圈依賴的問題。A 說:“我有一個類 A,它依賴於類 B;而類 B 又依賴於類 A。這就是一個典型的迴圈依賴問題。” B 說:“這很簡單,我們只需要用 Spring 解決它。” A 同意了,於是他們把程式碼放進 Spring 容器裡,結果......程式報了一個棧溢位錯誤!為什麼呢?因為 Spring 在解決迴圈依賴時需要使用棧來儲存物件的建立過程,如果迴圈依賴鏈過長,就會出現棧溢位的情況。

在看完上述的小故事後,小嶽給大家帶來一個簡單的程式碼案例,它展示了一個不能被 Spring 解決的迴圈依賴問題。假設有兩個類 A 和 B,它們相互依賴。A 依賴於 B 的例項,而 B 又依賴於 A 的例項。程式碼如下:

public class A {
private B b;
public A(B b) {
this.b = b;
}
}

public class B {
private A a;
public B(A a) {
this.a = a;
}
}

如果我們將這兩個類放進 Spring 容器中,程式碼如下:

@Configuration
public class AppConfig {
@Bean
public A a() {
return new A(b());
}

@Bean
public B b() {
return new B(a());
}
}

當我們啟動應用程式時,Spring 會嘗試建立 A 和 B 的例項。但是,當建立 A 的例項時,它需要一個 B 的例項,於是 Spring 開始建立 B 的例項。但是,當建立 B 的例項時,它需要一個 A 的例項,於是 Spring 又開始建立 A 的例項。這樣就形成了一個無限迴圈的依賴關係,最終導致棧溢位錯誤。

最後小嶽又要來囉嗦了,儘管 Spring 可以解決大部分的迴圈依賴問題,但是在某些特殊場景下,仍然會出現無法解決的迴圈依賴問題。 比如上面的 Java 程式碼案例中,如果 A 和 B 之間的依賴關係比較複雜,就可能出現無法解決的情況。在這種情況下,我們就需要手動來調整程式碼了哦,或者使用其他的依賴注入框架來解決迴圈依賴問題。

說到這裡肯定會有很多朋友說,你介紹了不能解決的,能不能吧能解決的也講講啊!當然可以,接下來就給大家總結下,Spring能解決什麼場景下的迴圈依賴問題吧!

2.3.2 Spring能解決的迴圈依賴問題

構造器注入迴圈依賴:

我們可以透過將其中一個bean作為引數傳遞給另一個bean的建構函式,Spring可以在bean建立過程中進行解決。經常有人問:為什麼Java開發人員都喜歡Spring?因為Spring可以幫助他們擺脫迴圈依賴的困擾啊。下面是小嶽給大家帶來的程式碼案例,大家認真學習哦!

public class A {
private B b;
public A(B b) {
this.b = b;
}
}

public class B {
private A a;
public B(A a) {
this.a = a;
}
}

@Configuration
public class AppConfig {
@Bean
public A a(B b) {
return new A(b);
}

@Bean
public B b(A a) {
return new B(a);
}
}

屬性注入迴圈依賴:

透過使用Setter方法注入屬性,Spring可以在bean建立過程中進行解決。搞笑的笑話:為什麼程式設計師不喜歡迴圈依賴?因為它們像兩個人彼此依賴卻都不敢先開口。Java程式碼案例:

public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}

public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}

@Configuration
public class AppConfig {
@Bean
public A a() {
return new A();
}

@Bean
public B b() {
return new B();
}

@Autowired
public void setDependencies(A a, B b) {
a.setB(b);
b.setA(a);
}
}

Spring提供了多種方式來解決迴圈依賴問題,其中就包括構造器注入和屬性注入。大家要記住使用Spring可以讓我們更加輕鬆地處理迴圈依賴問題,提高開發效率哦!

現在關於迴圈依賴的問題,大家都瞭解的差不多了吧,大家跟著小嶽就繼續向下學習吧!

3. Spring如何解決迴圈依賴問題?

透過上文我們瞭解到了什麼是迴圈依賴、為什麼會產生迴圈依賴以及Spring能解決哪些迴圈依賴問題和不能解決哪些迴圈依賴問題,講了這麼多,最後我們就把我們們標題中的問題:Spring如何解決Bean迴圈依賴問題給大家做做總結吧:

在上述小嶽帶大家學習的過程中,大家都清楚了Spring是一個流行的Java框架,它提供了許多功能來幫助開發人員構建應用程式。其中一個重要功能是處理Bean之間的依賴關係。在Java應用程式中,可能會出現迴圈依賴的情況,這意味著兩個或多個Bean之間互相依賴,形成了一個迴圈。這會導致應用程式無法正常啟動或出現其他問題。Spring提供了一種機制來解決Bean之間的迴圈依賴。

當然為了讓大家身臨其境的學習這個問題,給大家講幾個笑話,讓大家放鬆放鬆吧!

大家知道為什麼計算機工程師總是困在電梯裡?因為他們總是按下Ctrl-Alt-Del!

大家可能覺得有點冷哈,大家繼續向下看吧!小嶽來給大家解釋解釋這個笑話:

這個笑話其實就涉及到了一個迴圈依賴的問題。當你按下Ctrl-Alt-Del組合鍵時,你會發現計算機似乎卡住了。 這是因為按下這個組合鍵會觸發一個系統操作,但是系統操作本身可能依賴於其他程式或程式,而這些程式或程式又依賴於系統操作。這就形成了一個迴圈依賴,導致系統無法繼續執行。在這種情況下,我們需要打破迴圈依賴,以便系統可以正常工作。

這下大家明白了嗎?是不是覺得沒有那麼枯燥啦!學習也可以很快樂的嘛!

很早之前我跟我的程式設計師好朋友諮詢過一個問題:“為什麼我總是在建立Bean時遇到了迴圈依賴的問題啊?好煩啊!”然後,我內個朋友回答:“因為你不善於解耦啊!”這個時候我還沒太明白,就問他這是什麼意思了,結果人家給我來句:“解耦就是像男女朋友一樣,互相喜歡但是不會互相依賴。”這一句話直接給我當時幹懵,不知道大家理解不!歡迎討論哦!

其實為瞭解決這個迴圈依賴問題啊,Spring提供了這幾種方式,大家跟著小嶽繼續來看吧!

● 方法一: 透過建構函式注入避免迴圈依賴問題

● 方法二: 使用@Lazy註解標記懶載入的Bean,延遲建立bean例項,以避免在建立bean的時候出現迴圈依賴

● 方法三: 使用@Autowired註解的屬性或者建構函式引數

下面小嶽帶大家身臨其境的理解一番Bean 迴圈依賴的問題:

兩個 Bean 相互依賴,所以它們相互打了個招呼:

Bean 1: “你好,我需要 Bean 2 的幫助。”

Bean 2: “你好,我需要 Bean 1 的幫助。”

結果,它們都無法建立,並丟擲 BeanCreationException 異常。

接下來我們使用一段程式碼案例來解釋一番:

假設有兩個類,A 和 B,它們互相依賴。如果 A 和 B 都使用建構函式注入,它們之間就會發生迴圈依賴。

class A {
private B b;

public A(B b) {
this.b = b;
}
}

class B {
private A a;

public B(A a) {
this.a = a;
}
}

這段程式碼就會丟擲 BeanCreationException異常,主要是因為Spring無法解決 A 和 B 的迴圈依賴。

為瞭解決這個問題,我們可以使用 @Autowired註解在屬性或者建構函式引數上來解決迴圈依賴。修改上面的程式碼如下:

class A {
@Autowired
private B b;
}

class B {
@Autowired
private A a;
}

這樣,Spring 就能夠正確地建立 A 和 B 例項,從而避免了迴圈依賴問題。

最後小嶽給大家總結下,Spring 中解決 Bean 迴圈依賴問題的方法包括建構函式注入、使用@Lazy註解懶載入和@Autowired註解屬性或者建構函式引數。在編寫程式碼時應儘量避免迴圈依賴,如果無法避免,就可以使用上述方法來解決問題哦!

4. 總結

至此,Spring如何解決Bean的迴圈依賴問題就給大家解釋完了,現在大家都記住了嗎?最後把今天的重點給大家複習總結一下哦!

4.1 什麼是迴圈依賴?

● 迴圈依賴是指兩個或多個模組或物件之間互相依賴的情況。當一個模組或物件依賴另一個模組或物件時,迴圈依賴就會發生。例如,模組A依賴模組B,同時模組B又依賴模組A,這就是迴圈依賴的一種情況。

● 迴圈依賴可能會導致程式出現各種問題,比如編譯錯誤、執行時錯誤、死鎖等。因此,避免迴圈依賴是編寫高質量軟體的重要方面之一。

● 為了避免迴圈依賴,開發者需要最佳化模組或物件之間的依賴關係,可以透過重新設計程式碼結構或引入中間層來實現。另外,使用依賴注入、反轉控制等技術也可以減少迴圈依賴的發生。

4.2 Spring如何解決迴圈依賴問題

● 方法一: 透過建構函式注入避免迴圈依賴問題

● 方法二: 使用@Lazy註解標記懶載入的Bean,延遲建立bean例項,以避免在建立bean的時候出現迴圈依賴

● 方法三: 使用@Autowired註解的屬性或者建構函式引數

相關文章