日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Ioc源码分析 之 Bean的加载(5):循环依赖处理(populateBean())

發布時間:2025/3/15 javascript 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Ioc源码分析 之 Bean的加载(5):循环依赖处理(populateBean()) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

首先回顧下Bean加載的主流程:

1.如果是單例模式,從factoryBeanInstanceCache 緩存中獲取BeanWrapper 實例對象并刪除緩存 2.調用 createBeanInstance() 實例化 bean 3.后置處理 4.單例模式的循環依賴處理 5.屬性填充 6.初始化 bean 實例對象 7.依賴檢查 8.注冊 DisposableBean

本文我們主要分析第4步:

一、循環依賴是什么?

循環依賴,其實就是循環引用,就是兩個或者兩個以上的 bean 互相引用對方,最終形成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A。如下圖所示:

Spring中的循環依賴,其實就是一個死循環的過程,在初始化 A 的時候發現依賴了 B,這時就會去初始化 B,然后又發現 B 依賴 C,跑去初始化 C,初始化 C 的時候發現依賴了 A,則又會去初始化 A,依次循環永不退出,除非有終結條件。

一般來說,Spring 循環依賴的情況有兩種:

  • 構造器的循環依賴
    對于構造器的循環依賴,Spring 是無法解決的,只能拋出 BeanCurrentlyInCreationException 異常表示循環依賴,所以下面我們分析的都是基于 field 屬性的循環依賴。
  • field 屬性的循環依賴
    我們之前提到,Spring 只解決 scope 為 singleton 的循環依賴。對于scope 為 prototype 的 bean ,Spring 無法解決,直接拋出 BeanCurrentlyInCreationException 異常。
    為什么 Spring 不處理 prototype bean 呢?其實如果理解 Spring 是如何解決 singleton bean 的循環依賴就明白了。這里先留個疑問,我們先來看下 Spring 是如何解決 singleton bean 的循環依賴的。

二、解決singleton循環依賴

在AbstractBeanFactory 的 doGetBean()方法中,我們根據BeanName去獲取Singleton Bean的時候,會先從緩存獲取。
代碼如下:

//DefaultSingletonBeanRegistry.java@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 從一級緩存緩存 singletonObjects 中加載 beanObject singletonObject = this.singletonObjects.get(beanName);// 緩存中的 bean 為空,且當前 bean 正在創建if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 加鎖synchronized (this.singletonObjects) {// 從 二級緩存 earlySingletonObjects 中獲取singletonObject = this.earlySingletonObjects.get(beanName);// earlySingletonObjects 中沒有,且允許提前創建if (singletonObject == null && allowEarlyReference) {// 從 三級緩存 singletonFactories 中獲取對應的 ObjectFactoryObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {//從單例工廠中獲取beansingletonObject = singletonFactory.getObject();// 添加到二級緩存this.earlySingletonObjects.put(beanName, singletonObject);// 從三級緩存中刪除this.singletonFactories.remove(beanName);}}}}return singletonObject; }

這段代碼涉及的3個關鍵的變量,分別是3個級別的緩存,定義如下:

/** Cache of singleton objects: bean name --> bean instance */ //單例bean的緩存 一級緩存 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** Cache of singleton factories: bean name --> ObjectFactory */ //單例對象工廠緩存 三級緩存 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/** Cache of early singleton objects: bean name --> bean instance */ //預加載單例bean緩存 二級緩存 //存放的 bean 不一定是完整的 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

getSingleton()的邏輯比較清晰:

1.首先,嘗試從一級緩存singletonObjects中獲取單例Bean。 2.如果獲取不到,則從二級緩存earlySingletonObjects中獲取單例Bean。 3.如果仍然獲取不到,則從三級緩存singletonFactories中獲取單例BeanFactory。 4.最后,如果從三級緩存中拿到了BeanFactory,則通過getObject()把Bean存入二級緩存中,并把該Bean的三級緩存刪掉。
  • 2.1、三級緩存
    看到這里可能會有些疑問,這3個緩存怎么就解決了singleton循環依賴了呢?
    先別著急,我們現在分析了獲取緩存的代碼,再來看下存儲緩存的代碼。 在 AbstractAutowireCapableBeanFactory 的 doCreateBean() 方法中,有這么一段代碼:
// AbstractAutowireCapableBeanFactory.java boolean earlySingletonExposure = (mbd.isSingleton() // 單例模式&& this.allowCircularReferences // 允許循環依賴&& isSingletonCurrentlyInCreation(beanName)); // 當前單例 bean 是否正在被創建 if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}// 為了后期避免循環依賴,提前將創建的 bean 實例加入到三級緩存 singletonFactories 中addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }

這段代碼就是put三級緩存singletonFactories的地方,其核心邏輯是,當滿足以下3個條件時,把bean加入三級緩存中:

1.單例 2.允許循環依賴 3.當前單例Bean正在創建

addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 方法,代碼如下:

// DefaultSingletonBeanRegistry.java protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}} }

從這段代碼我們可以看出,singletonFactories 這個三級緩存才是解決 Spring Bean 循環依賴的關鍵。同時這段代碼發生在 createBeanInstance(...) 方法之后,也就是說這個 bean 其實已經被創建出來了,但是它還沒有完善(沒有進行屬性填充和初始化),但是對于其他依賴它的對象而言已經足夠了(已經有內存地址了,可以根據對象引用定位到堆中對象),能夠被認出來了。

  • 2.2、一級緩存
    到這里我們發現三級緩存 singletonFactories 和 二級緩存 earlySingletonObjects 中的值都有出處了,那一級緩存在哪里設置的呢?在類 DefaultSingletonBeanRegistry 中,可以發現這個 addSingleton(String beanName, Object singletonObject) 方法,代碼如下:
// DefaultSingletonBeanRegistry.java 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);} }

該方法是在 #doGetBean(...) 方法中,處理不同 scope 時,如果是 singleton調用的,如下圖所示:

也就是說,一級緩存里面是完整的Bean。
小結:

1.一級緩存里面是完整的Bean,是當一個Bean完全創建后才put
2.三級緩存是不完整的BeanFactory,是當一個Bean在new之后就put(沒有屬性填充、初始化)
3.二級緩存是對三級緩存的易用性處理,只不過是通過getObject()方法從三級緩存的BeanFactory中取出Bean

總結

現在我們再來回顧下Spring解決單例循環依賴的方案:

1.Spring 在創建 bean 的時候并不是等它完全完成,而是在創建過程中將創建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 三級緩存中)。
2. 這樣,一旦下一個 bean 創建的時候需要依賴 bean,則從三級緩存中獲取。

舉個栗子:

比如我們團隊里要報名參加活動,你不用上來就把你的生日、性別、家庭信息什么的全部填完,你只要先報個名字,統計下人數就行,之后再慢慢完善你的個人信息。

核心思想:提前暴露,先用著

最后來描述下就上面那個循環依賴 Spring 解決的過程:

  • 首先 A 完成初始化第一步并將自己提前曝光出來(通過 三級緩存 將自己提前曝光),在初始化的時候,發現自己依賴對象 B,此時就會去嘗試 get(B),這個時候發現 B 還沒有被創建出來
  • 然后 B 就走創建流程,在 B 初始化的時候,同樣發現自己依賴 C,C 也沒有被創建出來
  • 這個時候 C 又開始初始化進程,但是在初始化的過程中發現自己依賴 A,于是嘗試 get(A),這個時候由于 A已經添加至緩存中(三級緩存 singletonFactories ),通過 ObjectFactory 提前曝光,所以可以通過ObjectFactory#getObject() 方法來拿到 A 對象,C 拿到 A 對象后順利完成初始化,然后將自己添加到一級緩存中
  • 回到 B ,B 也可以拿到 C 對象,完成初始化,A 可以順利拿到 B 完成初始化。到這里整個鏈路就已經完成了初始化過程了
  • 濃縮成一張圖:

    最后,為什么多例模式不能解決循環依賴呢?
    因為多例模式下每次new() Bean都不是一個,如果按照這樣存到緩存中,就變成單例了。

    文章參考:https://juejin.im/post/5e927e27f265da47c8012ed9
    文章轉自:https://cloud.tencent.com/developer/article/1520922

    總結

    以上是生活随笔為你收集整理的Spring Ioc源码分析 之 Bean的加载(5):循环依赖处理(populateBean())的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。