編碼解決
通過良好的編碼習慣可以避免Spring循環依賴
方式主要有三種:
1. 構造函數注入: 這是最佳實踐,通過構造函數注入可以避免循環依賴。在一個類的構造函數中注入其依賴,而不是通過字段注入。
@Service
public class A {
private final B b;
@Autowired
public A(B b) {
this.b = b;
}
}
@Service
public class B {
private final A a;
@Autowired
public B(A a) {
this.a = a;
}
}
2. Setter方法注入: 使用Setter方法注入可以解決一部分循環依賴的問題,但不如構造函數注入優雅。
@Service
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
}
}
@Service
public class B {
private A a;
@Autowired
public void setA(A a) {
this.a = a;
}
}
3. @Lazy注解: 通過在其中一個依賴上使用@Lazy注解,可以延遲初始化被注入的Bean,從而解決循環依賴。
@Service
public class A {
@Autowired
@Lazy
private B b;
}
@Service
public class B {
@Autowired
@Lazy
private A a;
}
為什么構造函數注入可以解決循環依賴?
Spring 容器創建一個 Bean 的過程包括以下步驟:
- 實例化 Bean 對象: Spring 容器根據配置或注解等方式,實例化一個 Bean 對象。這一步是通過調用 Bean 對應類的構造函數完成的。
構造函數注入能夠解決循環依賴的原因主要是因為在 Java 對象創建的過程中,構造函數是最先被調用的,而在構造函數中完成了對象的初始化工作。通過構造函數注入,Spring 在創建對象時可以確保依賴的對象已經初始化完畢,避免了循環依賴的問題。
- 依賴注入: 容器對 Bean 進行屬性注入。這可以通過構造函數注入、Setter 方法注入或字段注入等方式完成。這是實現控制反轉(IoC)的關鍵步驟,將依賴關系從應用程序代碼中解耦,由容器負責管理。
因為通過構造函數注入,在依賴注入環節需要注入的Bean在上一個實例化階段就初始化完畢了,就不存在循環依賴問題了
BeanPostProcessor 處理: 如果在容器中注冊了 BeanPostProcessor,Spring 會在實例化 Bean 以及依賴注入之后調用它們的回調方法。這提供了一種機制,使開發者可以在 Bean 實例創建的不同生命周期階段進行定制操作。
初始化方法調用: 如果 Bean 實現了 InitializingBean 接口,或者在配置文件中通過 <init-method> 或 @PostConstruct 注解指定了初始化方法,Spring 會在依賴注入之后調用初始化方法。這個階段是為了讓開發者執行一些初始化邏輯。
BeanPostProcessor 處理(后置初始化): 如果注冊了 BeanPostProcessor,Spring 會在初始化方法調用后再次調用它們的回調方法。這一步稱為后置初始化,提供了對 Bean 進行最后處理的機會。
將 Bean 放入容器: 容器將創建完成并初始化的 Bean 放入自己的 Bean 容器中,以便供其他 Bean 或組件進行依賴注入。
那三級緩存解決循環依賴是怎么回事?
Spring 中通過三級緩存(三級Map)的方式來解決循環依賴問題。這三級緩存的主要作用是在處理 Bean 的創建過程中,記錄 Bean 的創建狀態,避免循環依賴導致的死循環。
三級緩存包括:
singletonObjects: 保存完全初始化并準備就緒的單例 Bean。在這個 Map 中,Bean 的名字作為鍵,Bean 實例作為值。在正常情況下,一個單例 Bean 只會放入該緩存一次。
earlySingletonObjects: 保存已經實例化但未完成初始化的 Bean。這是為了解決循環依賴的問題。在這個階段,Bean 實例被提前暴露給其他 Bean,但尚未執行完整的初始化。同樣,Bean 的名字作為鍵,Bean 實例作為值。
singletonFactories: 保存 Bean 工廠的提供者,即創建 Bean 的工廠對象。同樣,Bean 的名字作為鍵,Bean 工廠實例作為值。這個階段主要用于處理循環依賴。
下面是 Spring 處理循環依賴的基本流程:
- 嘗試獲取 Bean:
首先,從 singletonObjects 中嘗試獲取 Bean。如果找到,直接返回。
如果 singletonObjects 中沒有找到,再嘗試從 earlySingletonObjects 中獲取 Bean。如果找到,直接返回。這是為了解決循環依賴的問題,即使 Bean 尚未完全初始化,也可以提前暴露給其他 Bean 使用。
- 創建 Bean:
如果在 singletonObjects 和 earlySingletonObjects 中都沒有找到 Bean,則從 singletonFactories 中獲取 ObjectFactory,然后使用工廠創建 Bean 的原始實例。
在創建過程中,將正在創建的 Bean 放入 earlySingletonObjects。
- 初始化 Bean:
完成 Bean 的創建和初始化后,將 Bean 從 earlySingletonObjects 移動到 singletonObjects 中,表示 Bean 創建完成。
所以,earlySingletonObjects 中的 Bean 是在 Bean 創建的過程中,尚未完成初始化的實例,目的是為了提前處理循環依賴的情況。
總結起來,earlySingletonObjects 在 Bean 創建的過程中使用,用于解決循環依賴。在整個生命周期中,Bean 的狀態會在這三個緩存之間轉移。