溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java?Spring怎么處理循環依賴

發布時間:2023-04-21 15:04:34 來源:億速云 閱讀:180 作者:iii 欄目:開發技術

今天小編給大家分享一下Java Spring怎么處理循環依賴的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

01-前言:什么是循環依賴?

首先,我們先明確下依賴的定義。 如果一個 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 ...
}

02-Spring 如何處理循環依賴?

在之前的文章中,我跟大家一塊學習了 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;
    }
}

03-Spring 中解決循環依賴的三級緩存

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怎么處理循環依賴”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女