盤點 SpringIOC : 循環依賴

總文檔 :文章目錄
Github : github.com/black-anthtml

一 . 前言

這是一篇嘗試型的文章 , SpringIOC 的筆記基本上已經整理出來了 , 可是始終沒有想好該怎麼輸出爲文檔 .java

在我看來 , 若是我花了時間整理出一篇文檔 , 那麼不論多久 , 哪怕有所遺忘 , 後面再讀的時候也應該能迅速讀懂 . 整個 IOC 體系會以這個思路去輸出 .git

IOC 整個體系過於龐大 , 哪怕把以前的筆記所有梳理完 , 仍然感受仍是不夠的 . 不過先挑個好說的 , 看看能不能先把這一部分梳理好 .github

二 . 功能現象

循環依賴是指三個對象互相依賴 , 當經過 getBean 去獲取依賴的 Bean 時 , 就造成了循環依賴緩存

// 這裏先使用 Scope 嘗試使用 prototype 模式 
@Component
@Scope(value = "prototype")
public class BeanCService{}

// 當 BeanA , BeanB , BeanC 相互依賴時 , 結果是這樣的 :
 The dependencies of some of the beans in the application context form a cycle:
   beanStartService (field private com.gang.BeanAService com.gang.BeanStartService.beanAService)
┌─────┐
|  beanAService (field private com.gang.BeanBService com.gang.BeanAService.beanBService)
↑     ↓
|  beanBService (field private com.gang.BeanCService com.gang.BeanBService.beanCService)
↑     ↓
|  beanCService (field private com.gang.BeanAService com.gang.study.BeanCService.beanAService)
└─────┘

複製代碼

去掉 @Scope(value = "prototype") 後 , 一切正常
循環依賴是在 單例模式中處理完成的markdown

三 . 源碼跟蹤

咱們從源碼的角度分析一下 , SpringIOC 的單例是怎麼控制循環依賴的併發

3.1 單例Bean 的加載流程

一切的起點app

起點固然是從 AbstractBeanFactory 開始的 , 這一步會在 Bean 建立時詳細說說 , 這裏先放過oop

C- AbstractBeanFactory 
    M- doGetBean :
        - Object sharedInstance = getSingleton(beanName) : 從緩存中獲取Bean
        
複製代碼

能夠看到 , 當建立第一個 BeanA 時 , 進入 DefaultSingletonBeanRegistrypost

// 繼續深刻 DefaultSingletonBeanRegistry , 其中有如下的參數
C180- DefaultSingletonBeanRegistry
    F01- private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
        ?- 單例對象的緩存:bean到bean實例的名稱
        
    F02- private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
        ?- 單例工廠的緩存:ObjectFactory的bean名
        
    F03- private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
        ?- 早期單例對象的緩存:bean到bean實例的名稱
        
    F04- private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
        ?- 一組已註冊的單例,按註冊順序包含bean名稱
        
    F05- private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));
        ?- 當前正在建立的bean的名稱
        
    F06- private final Set<String> inCreationCheckExclusions =Collections.newSetFromMap(new ConcurrentHashMap<>(16));
        ?- 當前在建立檢查中被排除的bean的名稱
        
    F07- private Set<Exception> suppressedExceptions;
        ?- 異常列表
        
    F08- private boolean singletonsCurrentlyInDestruction = false;
        ?- 標誌是否正在銷燬單例
        
    F09- private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
        ?- 一次性bean實例:從bean名稱到一次性實例
        
    F10- private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<>(16);
        ?- 包含bean名稱之間的映射:bean名稱到bean所包含的一組bean名稱
        
    F11- private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
        ?- 依賴bean名稱之間的映射:bean名稱到一組依賴bean名稱
        
    F12- private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
        ?- 依賴bean名稱之間的映射:bean名稱到bean依賴項的一組bean名稱

複製代碼

怎麼建立一個單例Bean

// 當咱們建立一個單例Bean 時
C180- DefaultSingletonBeanRegistry      
    M180_01- getSingleton(String beanName, boolean allowEarlyReference)
        - 首先從 Map<String, Object> singletonObjects  中獲取
        - 若是沒有 , 且正在建立 (->M180_02) , 則繼續從 earlySingletonObjects 中獲取
        - 若是仍是沒有 , 且須要建立早期引用 , 則繼續從 this.singletonFactories 中獲取
        -若是 singletonFactories 獲取到了  , 將當前 bean 放入 singletonObjects 且從 singletonFactories 移除
    M180_02- isSingletonCurrentlyInCreation
        ?- 判斷當前 Bean 是否已經在註冊
        - singletonsCurrentlyInCreation.contains(beanName)
    
    
// M180_01 代碼 : 分別從三個 ConcurrentHashMap 中獲取對應的 Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}


複製代碼

從整個流程來看 , 最主要的就是從三個 CurrentHashMap 本地緩存中獲取 Bean 實例

PS : 這裏能夠預先說一下 , 當第一個Bean 建立時 , 就已經在緩存總存在了 , 哪怕他尚未徹底加載完成

sequenceDiagram
BeanA->> BeanB: Get(B) 獲取 BeanB
BeanB->>BeanC: Get(C) 獲取 BeanC
BeanC-->>BeanA: Get(A) 獲取 BeanA
BeanC->> singletonObjects : 從singletonObjects 中第一次獲取獲取 BeanA
singletonObjects->> BeanC : 獲取失敗 ,且已建立
BeanC->> earlySingletonObjects : 從緩存中Get(A) 獲取 BeanA
earlySingletonObjects->> BeanC :獲取失敗 ,且須要提早建立
BeanC->> singletonFactories : 從緩存中Get(A) 獲取 BeanA

咱們就這幾個緩存來好好的看一下 :

繼續向下檢索 ,看一下 上面2個對象是何時放上去的

// 着重複習一下上面幾個對象
 
singletonsCurrentlyInCreation : 當前正在建立的 Bean 名稱集合
singletonObjects : 單例對象的緩存
earlySingletonObjects : 早期單例對象的緩存
singletonFactories : 單例工廠的緩存
複製代碼

3.2 singletonsCurrentlyInCreation

Step 1 : singletonsCurrentlyInCreation 插入的地方

C180- DefaultSingletonBeanRegistry      
    M180_03- beforeSingletonCreation
    	?- this.singletonsCurrentlyInCreation.add(beanName) : 添加當前 BeanName
    M180_04- afterSingletonCreation
    	?- this.singletonsCurrentlyInCreation.remove(beanName) : 移除當前 BeanName
    
// 這裏大概就清楚了 插入的方式
// PS : 如下方法中都會調用 beforeSingletonCreation 方法 , 三個方法處理完成後 , 就會移除
C- AbstractAutowireCapableBeanFactory
    M- getSingletonFactoryBeanForTypeCheck 
C- DefaultSingletonBeanRegistry
    M- getSingleton :  單例Bean 建立的位置
C- FactoryBeanRegistrySupport
    M- getObjectFromFactoryBean   
複製代碼

3.3 singletonObjects 的存儲

singletonObjects 存入的位置

M180_05- addSingleton
    - this.singletonObjects.put(beanName, singletonObject)
        ?- 也就是說每添加一個 單例 ,都會往其中添加一個對象
    M- getSingletonMutex : 這是惟一一個有可能放入數據的地方了
        - return this.singletonObjects;

// 打斷點跟蹤後發現如下類中進行了調用: 
C- FactoryBeanRegistrySupport
    M- getObjectFromFactoryBean : 只是做爲一個鎖對象
C- AbstractApplicationEventMulticaster
    M- setBeanFactory 
        - this.retrievalMutex = this.beanFactory.getSingletonMutex() : 將對象進行了引用
        ?- 其中也大部分做爲鎖對象 , 沒有進行操做
複製代碼

PS : 使用該對象做爲 synchronized 的監視對象能夠有效保證惟一性

一開始我在想 ConcurrentHashMap 應該也能夠保證併發惟一 , 後來想了一下 , 這裏的惟一應該是業務流程上的惟一 ,例如我這裏要刪除 , 你那裏就不能進行添加 , 等我作完了 , 你才能繼續

singletonObjects 刪除的地方

C180- DefaultSingletonBeanRegistry  
    M180_07- clearSingletonCache
        - 這裏不止一個集合 , 是把全部的集合都清空了
        ?- 看了一下 , 主要是再 destroySingletons 流程中調用
    M180_08- removeSingleton
        - 移除單個單例時 ,此時時 remove
複製代碼

PS : 也就是說 , 銷燬單例時 , 該數據會被清空或者移除

// M180_07 代碼
protected void clearSingletonCache() {
    synchronized (this.singletonObjects) {
        this.singletonObjects.clear();
        this.singletonFactories.clear();
        this.earlySingletonObjects.clear();
        this.registeredSingletons.clear();
        this.singletonsCurrentlyInDestruction = false;
    }
}
複製代碼
sequenceDiagram
addSingleton->> singletonObjects : addSingletonFactory 時 put進入參數
Note left of singletonObjects: singletonObjects 集合
clearSingletonCache-->>singletonObjects : clearSingletonCache 方法中移除
removeSingleton-->>singletonObjects : removeSingleton 方法中移除

3.4 earlySingletonObjects

earlySingletonObjects 保存的地方

C180- DefaultSingletonBeanRegistry  
    M180_01- getSingleton(String beanName, boolean allowEarlyReference)
    	?- 仍是以前獲取的位置 , 能夠看到 , 這裏有一個參數是 allowEarlyReference
    	- this.earlySingletonObjects.put(beanName, singletonObject)
複製代碼

PS : 這個的位置爲 初始類 BeanA 加載時 , 此時未注入依賴 , 只是第一次構建

earlySingletonObjects 是一箇中間緩存 , 在Single Bean 添加完成後 , 這個中間緩存就會被清除

earlySingletonObjects 清除的地方

C180- DefaultSingletonBeanRegistry     
    M180_09- addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) : 清除單個
    M180_07- clearSingletonCache : 清空全部
    M180_05- addSingleton : 清除單個

複製代碼
sequenceDiagram
getSingleton->> earlySingletonObjects : getSingleton 時 put進入參數
Note left of earlySingletonObjects: earlySingletonObjects 集合
addSingletonFactory-->>earlySingletonObjects : addSingletonFactory 方法中移除
clearSingletonCache-->>earlySingletonObjects : clearSingletonCache 方法中 clear
addSingleton-->>earlySingletonObjects : addSingleton 方法中 移除

3.5 singletonFactories

singletonFactories 是單例工廠

// 仍是先看下
C180- DefaultSingletonBeanRegistry      
    M180_07- addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)
    	IF- 若是 singletonObjects (F01)包含 當前 BeanName
            TRUE- singletonFactories (F02) 添加當前 Bean 和 singletonFactory
                ?- this.singletonFactories.put(beanName, singletonFactory);

// PS : 單例工廠是哪一個環節被調用的
首先 , getSingle 中 , 實際上接的是一個 lambda 表達式
C180- DefaultSingletonBeanRegistry      
    M180_01- getSingleton(String beanName, boolean allowEarlyReference)    
        - ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        - singletonObject = singletonFactory.getObject();

// 該對象是在 AbstractAutowireCapableBeanFactory # doCreateBean 中生成的
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

// 其中傳入了一個 getEarlyBeanReference 方法 , 該方法會在 getObject 回調
() -> getEarlyBeanReference(beanName, mbd, bean) --- singletonFactory.getObject() 
// 最終會經過 SmartInstantiationAwareBeanPostProcessor 來處理生成
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);    

// 最終會生成一個空的對象 , 放進去

複製代碼

PS : 該對象會在後續中被 getSingleton 獲取繼續處理

sequenceDiagram
DefaultSingletonBeanRegistry->> singletonFactories : addSingletonFactory 時 put進入參數
Note left of singletonFactories: singletonFactories 集合
getSingleton-->>singletonFactories : singletonFactories 方法中移除
removeSingleton-->>singletonFactories : removeSingleton 方法中移除
clearSingletonCache-->>singletonFactories : clearSingletonCache 方法中 clear

image-20210421095922482.png

image-20210421100927098.png

3.6 爲何 prototype 不可行

這裏涉及到 prototype 的加載過程 :

prototype 的建立是在 AbstractBeanFactory 中完成

C171- AbstractBeanFactory 
    M171_02- doGetBean( String name, Class<T> requiredType,Object[] args, boolean typeCheckOnly)
        - 若是是 Singleton , 則 getSingleton
            ?- 走了緩存集合
        - 若是是 Prototype , 則 createBean
            ?- 這裏是沒有緩存類來處理的
    
// M171_02 僞代碼
if (mbd.isSingleton()) {
    //.....
} else if (mbd.isPrototype()) {
    Object prototypeInstance = null;
    try {
        beforePrototypeCreation(beanName);
        prototypeInstance = createBean(beanName, mbd, args);
    }finally {
        afterPrototypeCreation(beanName);
    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}


複製代碼

完整流程

sequenceDiagram
BeanC->> singletonObjects : 從singletonObjects 中第一次獲取獲取 BeanA
singletonObjects->> BeanC : 獲取失敗 ,且已建立
BeanC->> earlySingletonObjects : 從緩存中Get(A) 獲取 BeanA
earlySingletonObjects->> BeanC :獲取失敗 ,且須要提早建立
BeanC->> singletonFactories : 從緩存中Get(A) 獲取 BeanA

補充資料 : SingletonBeanRegistry 體系

SingletonBeanRegistry001.jpg

總結

循環依賴的處理流程其實很簡單 , 篇幅也不長 , 主要就是幾個緩存集合的使用.

這篇主要是想看看能不能使文檔具備快速會議的能力

參考文獻

cmsblogs.com/?cat=206

www.cnblogs.com/qinzj/p/114…

附錄

相關文章
相關標籤/搜索