循環依賴通常發生在兩個或多個Spring Bean之間,它們通過構造器、字段(使用@Autowired)或setter方法相互依賴,從而形成一個閉環。下面是一個使用字段注入(即使用@Autowired)導致的循環依賴的示例:
示例代碼:
假設我們有兩個類,ClassA 和 ClassB,它們相互依賴:
public class ClassA { @Autowired private ClassB classB; // ... 其他代碼 ... } @Component public class ClassB { @Autowired private ClassA classA; // ... 其他代碼 ... }
在上面的示例中,ClassA 依賴 ClassB,而 ClassB 又依賴 ClassA。當Spring容器啓動時,它會嘗試爲這兩個類創建bean的實例。但是,由於它們之間的循環依賴,這會導致問題。
問題說明:
當Spring容器創建ClassA的bean時,它會發現ClassA依賴於ClassB,所以它會嘗試創建ClassB的bean。當Spring容器創建ClassB的bean時,它又會發現ClassB依賴於ClassA,但此時ClassA的bean還沒有被完全初始化(因爲它正在等待ClassB的bean),這就形成了一個死循環。
那麼Spring是如何解決這種循環依賴問題的?
三級緩存機制:
Spring容器在創建bean的過程中,會維護三個緩存,分別是:singletonObjects(一級緩存)、earlySingletonObjects(二級緩存)和singletonFactories(三級緩存)。
當Spring容器開始實例化一個bean時,會先將其ObjectFactory(對象工廠)放入三級緩存中。如果在實例化過程中需要注入其他bean,並且這個bean也正在實例化中,Spring會先從一級緩存中查找該bean的實例。如果沒有找到,會到二級緩存中查找。如果二級緩存中也沒有,那麼會到三級緩存中查找ObjectFactory,並調用ObjectFactory的getObject方法來獲取bean的實例。
當獲取到bean的實例後,會將其放入二級緩存中,並從三級緩存中移除ObjectFactory。
當bean的實例化過程完成後,會將其放入一級緩存中。
通過這種方式,Spring可以在bean的實例化過程中解決循環依賴問題。
@Lazy註解:
在Spring中,可以使用@Lazy註解來延遲bean的初始化。當一個bean被標記爲@Lazy時,Spring容器在啓動時不會立即實例化它,而是在第一次被使用時才進行實例化。
通過將循環依賴的bean聲明爲懶加載,可以延遲它們的初始化過程,從而避免在容器啓動時發生循環依賴問題。
需要注意的是,@Lazy註解只能用於單例作用域的bean,並且要求依賴項必須是接口類型。
以下是使用@Lazy註解來解決循環依賴的示例代碼:
@Component public class ClassA { private final ClassB classB; // 使用@Autowired和@Lazy註解來延遲ClassB的注入 @Autowired public ClassA(@Lazy ClassB classB) { this.classB = classB; } // ... 其他代碼 ... } @Component public class ClassB { private final ClassA classA; // 使用@Autowired和@Lazy註解來延遲ClassA的注入 @Autowired public ClassB(@Lazy ClassA classA) { this.classA = classA; } // ... 其他代碼 ... }
在上面的示例中,@Lazy註解被用於ClassA和ClassB的構造器參數上,以延遲它們之間的依賴注入。這意味着在創建ClassA時,它不會立即嘗試去初始化ClassB,而是會得到一個代理對象。同樣,在創建ClassB時,它也不會立即初始化ClassA。
避免構造器循環依賴:
在Spring中,構造器循環依賴是無法解決的,因爲構造器在實例化bean的過程中被調用,如果兩個bean相互依賴對方的構造器,那麼就會形成死鎖。
因此,在設計bean的依賴關係時,應該儘量避免使用構造器注入來創建循環依賴。可以使用setter注入或字段注入來代替構造器注入。
下面是一個構造器循環依賴的錯誤代碼示例:
@Component public class ClassA { private final ClassB classB; // 通過構造器注入ClassB,形成循環依賴 @Autowired public ClassA(ClassB classB) { this.classB = classB; } // ... 其他代碼 ... } @Component public class ClassB { private final ClassA classA; // 通過構造器注入ClassA,與ClassA形成循環依賴 @Autowired public ClassB(ClassA classA) { this.classA = classA; } // ... 其他代碼 ... }
在上面的示例中,ClassA和ClassB各自在構造函數中依賴於對方,這就形成了構造器循環依賴。
當Spring容器嘗試創建這兩個bean的實例時,會遇到問題:
-
Spring首先嚐試創建ClassA的實例,並發現它需要ClassB的實例。
-
然後,Spring嘗試創建ClassB的實例,但發現它需要ClassA的實例。
-
由於ClassA的實例正在等待ClassB的實例,而ClassB的實例又正在等待ClassA的實例,這導致了一個死循環,無法繼續創建bean的實例。
綜上所述,Spring通過三級緩存機制、@Lazy註解以及避免構造器循環依賴等方式來解決循環依賴問題。這些機制使得Spring容器能夠更加靈活地處理bean之間的依賴關係,提高系統的可維護性和可擴展性。