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