今天小編給大家分享一下Java Spring怎么處理循環依賴的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
首先,我們先明確下依賴的定義。 如果一個 Bean bar 的屬性,引用了容器中的另外一個 Bean foo,那么稱 foo 為 bar 的依賴,或稱 bar 依賴 foo。 如果用代碼表示,可以表示為:
@Component("foo")
public Class Foo {
@Autowired
private Bar bar; // 稱 foo 依賴 bar
}
@Component("bar")
public Class Bar {
@Autowired
private Baz baz; // bar 依賴 baz
}其次,循環引用的概念是指多個 Bean 之間的依賴關系形成了環。 接著上面的例子,如果 baz 依賴 foo 或 bar 都將形成循環依賴。
@Component("baz")
public class Baz {
@Autowired
private Foo foo; // 形成了循環依賴,baz -> (依賴) foo -> bar -> baz ...
}在之前的文章中,我跟大家一塊學習了 Spring 創建 Bean 過程的源碼。 我們知道:在 createBeanInstance 階段,需要解決構造器、工廠方法參數的依賴; 在 populateBean 階段,需要解決類屬性中對其他 Bean 的依賴。 這其實對應了 Spring 中支持的兩種依賴注入方式,基于構造器的依賴注入和基于 setter 方法的依賴注入,分別對應前面的兩種情況。
接下來,我會通過上節介紹的示例,來分情況討論產生循環依賴的場景。 為了使討論過程更清楚、更簡潔,我會讓 foo 依賴 bar,而 bar 依賴 foo。 在接下來的描述中,我假設 Spring 會先創建 Bar 的對象,再創建 Foo 的對象。 針對不同的依賴情況,可以分為四種場景:
第一種場景,Bar 在構造器參數中依賴 Foo,Foo 在構造器參數中依賴 Bar。這種場景下,依賴的注入發生在 Bar 和 Foo 的實例化階段。
第二種場景,Bar 在構造器參數中依賴 Foo,Foo 通過 setter 函數依賴 Bar。這種場景下,Bar 中注入 Foo 發生在實例化階段,Foo 中注入 Bar 發生在屬性填充階段。
第三種場景,Bar 通過 setter 函數依賴 Foo,Foo 在構造器參數中依賴 Bar。這種場景下,Bar 中輸入 Foo 發生在屬性填充階段,而 Foo 中注入 Bar 發生在實例化階段。
第四種場景,Bar 通過 setter 函數依賴 Foo,Foo 通過 setter 函數依賴 Bar。 這種場景下,依賴注入均發生在屬性填充階段。
在具體分析上述四種場景之前,先說下結論: Spring 可以解決場景三、四中出現循環依賴的情況,而第一、二種場景,Spring 無法解決,需要重構依賴或者延遲延遲依賴注入的時機(例如使用 @Lazy 等)。 細心的讀者可能會問第二種、第三種場景有什么不同呢? 其實第二、第三種場景本質上是同一種情況,唯一的不同是實例化的先后順序。 結合這個信息,可以得出,先創建的類以構造器參數方式依賴其他 Bean,則會發生循環依賴異常。 反過來,如果先創建的類以 setter 方式依賴其他 Bean,則不會發生循環依賴異常。
接下來,我會詳細分析每一種場景,并指出拋循環依賴異常的時機。
首先,所有的單例 Bean 會在容器啟動后被創建 ConfigurableListableBeanFactory#preInstantiateSingletons,即所謂的 “eager registration of singletons” 過程。
第一種場景,會先觸發 Bar 類的實例 bar 的創建。在 createBeanInstance 階段,會通過 ConstructorResolver#autowireConstructor 來創建實例。 ConstructorResolver#createArgumentArray 會解析構造器中的參數,并處理對其他 Bean 依賴的引用 ConstructorResolver#resolveAutowiredArgument。 處理依賴的方式就是通過 DefaultListableBeanFactory#resolveDependency 來查找符合條件的 Bean,最終還是通過 AbstractBeanFactory#getBean 來從容器中取。 當通過 getBean("bar") 來觸發 Spring 創建 bar 時,在實例化階段,根據構造器參數來 getBean("foo") 并觸發 foo 的創建。 在 foo 的實例化過程與 bar 的是完全一樣的,最終 getBean("bar")。這是容器中的 bar 還沒有創建好,所以會再次觸發創建過程。 在真正創建過程之前,在 DefaultSingletonBeanRegistry#getSingleton 中會有一次檢查,DefaultSingletonBeanRegistry#beforeSingletonCreation 如果發現要創建的 bean 正在創建過程中,則拋出異常。
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bar': Requested bean is currently in creation: Is there an unresolvable circular reference?
第二種場景,同樣先構建 bar 實例。與第一種場景不同之處在于 foo 創建時,它的 createBeanInstance 階段能夠執行完畢。 原因是 foo 只有一個無參構造器(即默認構造器),不需要注入其他依賴。 foo 的 createBeanInstance 階段執行完畢后,會進入 populateBean 階段。 在這個階段中,AutowiredAnnotationBeanPostProcessor#postProcessProperties 會處理 setter 函數依賴的 Bean。 大致處理過程為:AutowiredAnnotationBeanPostProcessor 識別到 foo 中包含需要注入依賴的 setter 函數,將其映射為 AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement 對象。 然后調用 AutowiredMethodElement#inject 方法注入依賴。 在 inject 方法中,會調用 DefaultListableBeanFactory#resolveDependency 來查找對應的依賴。 到這里為止,后續的過程與第一種場景完全一致了。 從容器中嘗試獲取 bar,發現不存在,會出發 bar 的再次創建,最終在 DefaultSingletonBeanRegistry#beforeSingletonCreation 中拋出異常。
第三種場景,同樣先構建 bar 實例。由于它只包含一個默認構造器,所以它的 createBeanInstance 階段會順利完成,然后進入 populateBean 階段。 當你仔細回看一下 Spring 創建 Bean 過程的源碼,你會發現下面這段代碼:
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}這段邏輯發生在 createBeanInstance 之后,尚未進入 populateBean 之前。 這里其實就是 Spring 解決循環依賴機制的核心點之一,這里我暫且不深入介紹,后面會有詳細的分析。
繼續前面的分析。bar 實例創建進入到 populateBean 階段后,會檢查其自身依賴情況,然后注入對應的依賴 Bean。 這里的處理邏輯依然是 AutowiredAnnotationBeanPostProcessor#postProcessProperties 來處理。 當嘗試注入 foo 時,會出發 foo 實例的創建過程。foo 通過構造器依賴 bar,因此在其 createBeanInstance 階段,會通過 ConstructorResolver#autowireConstructor 完成依賴注入。 此時通過 getBean("bar") 從容器中嘗試獲取 bar 時,能夠“獲取到”。 注:這里為什么不會出發 bar 的創建,反而能夠直接得到 bar 對象呢?上面的獲取到我加了引號,它其實獲得的并不是一個完整、可用的 bar。 它獲得的是通過 earlySingletonExposure 提前暴露出的對象。 這個過程在后面介紹三級緩存時會詳細介紹。
篇幅原因,第四種場景我不在這里繼續分析,感興趣的讀者可以自己嘗試分析下。 簡單提示下,它的過程有點像第三種場景前半段、第二種場景的后半段結合起來。
在上述四種場景下,第一種情況,依賴雙方都是通過構造器依賴對方,這種情況下 Spring 是無法處理的。 而且,我認為出現這一情況,屬于是設計上的缺陷,應當通過重新設計依賴關系來解決,例如可以將基于構造器的注入修改為基于 setter 的注入,或者通過 @Lazy 將依賴的初始化延遲到使用時。 通過 Foo、Bar 類來舉例說明。
@Component
public class Foo {
private Bar bar;
@Autowired
public Foo(@Lazy Bar bar) {
this.bar = bar;
}
}
@Component
public class Bar {
private Foo foo;
@Autowired
public Bar(Foo foo) { // 或者將對 foo 的依賴,注解為 @Lazy 表示使用時才初始化
this.foo = foo;
}
}另一種修改方式就是,將第一種情況,修改為第二種情況,即:
@Component
public class Bar {
private Foo foo;
@Autowired
public void setFoo(Foo foo) {
this.foo = foo;
}
}Spring 中設計了一個三級緩存用來解決前面介紹的循環依賴問題的處理。三級緩存包括:
singletonObjects,為一級緩存,保存了 beanName -> bean instance 的映射關系。存放的是完全可用的單例 Bean 對象。
earlySingletonObjects,為二級緩存,保存了 beanName -> bean instance 的映射關系。 在一級、二級緩存都沒有發現目標對象,但三級緩存中存在 ObjectFactory 對象時,調用 ObjectFactory#getObject 創建實例,放入二級緩存,刪除三級緩存中的 ObjectFactory 對象。
singletonFactories,為三級緩存,保存了 beanName -> ObjectFactory 的映射關系。 在 doCreateBean 時,會向這個 map 中添加 beanName: () -> getEarlyBeanReference(beanName, mbd, bean) 的映射關系,value 是一個函數式接口 ObjectFactory。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}大概了解了 Spring 中的三級緩存后,我們再回過頭來看一下 AbstractBeanFactory#getBean 過程。 它的實際工作是在 AbstractBeanFactory#doGetBean 中完成的。 doGetBean 方法的具體實現可以簡化、抽象為:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 緩存中存在
/**
* 如果 beanName 是一個 FactoryBean,則獲取對應的 Bean
* 如果 beanName 是一個普通的 Bean,則返回這個 Bean 本身
*/
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 緩存中不存在
// 創建對象
if (mbd.isSingleton()) {
/**
* 這里的 getSingleton 是 getSingleton(beanName) 的重載版本
* 它接受一個 beanName 和 一個 ObjectFactory 作為參數
* 調用 ObjectFactory#getObject 產生一個實例
* 并通過 addSingleton(beanName, singletonObject); 將實例添加到 singletonObjects 中
* 這里 createBean 的代碼就是前面提到的 Spring 創建 Bean 實例的過程 doCreateBean
*/
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
});
/**
* 同前面分支中的作用一樣
*/
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
/**
* 判斷 Bean 類型與 requiredType 類型是否一直,一致則直接返回,不一致則需要進行轉換
*/
return adaptBeanInstance(name, beanInstance, requiredType);
}從上面的源碼可以知道,當 DefaultSingletonBeanRegistry#getSingleton(beanName) 時,會先從多級緩存中取對象(可能是 bean instance,也可能是對應的 ObjectFactory)。 從多級緩存中取對象的源碼如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
/**
* 判斷第一級緩存中是否有完全可以可用的 Bean 實例,若有則返回;
* 若沒有,則根據情況判斷
* isSingletonCurrentlyInCreation(beanName) 檢查的是在 `Set<String> singletonsCurrentlyInCreation` 集合中是否包含要獲取的 Bean 實例
* beanName 只在調用 beforeSingletonCreation(String beanName) 時被添加到 singletonsCurrentlyInCreation 集合中
* beforeSingletonCreation 在創建 bean,即 doCreateBean 之前調用,在創建過程完成以后,調用 afterSingletonCreation 從集合中移除 beanName
*/
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
/**
* 執行到這個分支,其實說明 Bean 已經在創建過程中了,只不過是尚未完全可用(即一級緩存中沒有)
* 檢查二級緩存,是否包含指定的 Bean
*
* 二級緩存里的內容何時被添加或設置進來的呢?
* 我們可以檢查下 earlySingletonObjects.put 方法都在哪里調用。
* 檢查后發現,其實 earlySingletonObjects 就是在當前方法中設置的,我們接著往下看。
*/
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
/**
* 這里的 allowEarlyReference 的意思就是指是否允許在二級緩存中創建一個對象,即是否允許暴露未完全可用的對象
* 如果 allowEarlyReference 為假,則不會操作二級、三級緩存,而僅檢查一級緩存中是否有完全可用的 Bean 實例
* 這也意味著,不允許返回未完全可用狀態的 Bean
*
* 當發現二級緩存中沒有對象,同時又允許提前引用(即 allowEarlyReference 值為真)
* 則檢查三級緩存中是否有對應的 ObjectFactory 對象,若有,則調用它的 getObject 方法產生對象,然后將其放置到二級緩存中,同時刪除三級緩存中的對象工廠實例
* 若三級緩存中也沒有對象工廠實例,則說面 bean 還未創建
*/
synchronized (this.singletonObjects) {
/**
* 這里會進行一個 double-check,避免多線程間的線程安全問題
*/
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
/**
* 三級緩存中存在對象工廠實例,則通過它產生一個 Bean 實例
* 加入到二級緩存中,同時刪除三級緩存中的對象工廠實例
*/
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}注:
isSingletonCurrentlyInCreation(beanName) 意味著什么呢?意味著對應的 Bean 在 doCreateBean 過程中,可能在 createBeanInstance \ populateBean \ initializeBean 階段中。
在前面提到,createBeanInstance 后,Bean 會被添加到上述多級緩存中的第三級緩存中,存入對象是 beanName -> objectFactory 映射關系。 當其他的 Bean 依賴當前 Bean 時,而且允許引用提前暴露的 Bean(即未完全可用的 Bean),會檢查第二級緩存,如果沒有還會檢查第三級緩存,并在得到對應 objectFactory 時,獲得對象并將其從第三級移動到第二級。
有些讀者看到這里可能會有個疑問,那二級緩存中的對象什么時候刪除呢? 我們再來回頭看下 doGetBean 中的代碼片段:
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
});這里的 singleton
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
/**
* 檢查 Bean 是否在創建過程中,避免重復創建,
* 不能解決的循環依賴也是在這里拋出異常
*/
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
/**
* 這里調用的其實就是 AbstractAutowireCapableBeanFactory#createBean
* 然后會執行 doCreateBean(三個階段)
*/
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
throw ex;
}
finally {
afterSingletonCreation(beanName);
}
if (newSingleton) {
/**
* 這里說明一個 bean 創建過程的三個階段都執行完畢了
*/
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
/**
* 將 Bean 實例添加到第一級緩存
* 將第二級、第三級緩存中的對象刪除
*/
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}以上就是“Java Spring怎么處理循環依賴”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。