3.4 spring5原始碼系列--迴圈依賴的設計思想

盛開的太陽發表於2020-11-15

前面已經寫了關於三篇迴圈依賴的文章, 這是一個總結篇

第一篇: 3.1 spring5原始碼系列--迴圈依賴 之 手寫程式碼模擬spring迴圈依賴

第二篇: 3.2spring原始碼系列----迴圈依賴原始碼分析

第三篇: 3.3 Spring5原始碼---迴圈依賴過程中spring讀取不完整bean的最終

現在總結迴圈依賴的思想

學了那麼多, 為什麼說見多才能識廣呢 , 知道別人是如何解決某一類問題的, 也就是優秀程式碼的魅力. 這也是為什麼要學習別人的程式碼的原因.

思想才是我們可以在工作中借鑑使用的

1. 迴圈依賴的三級快取設計

2. 介面函式

 


 

一. 迴圈依賴的三級快取設計

再迴圈依賴的過程中設計了三級快取, 他們的作用分別是

1. 一級快取: 用來存放完整的bean

2. 二級快取: 用來存放早期的,純淨的bean

3. 三級快取: 用來存放介面函式.

   /** Cache of singleton objects: bean name to bean instance. */
    /**
     * 一級快取  這個就是我們大名鼎鼎的快取池, 用於儲存我們所有的例項bean
     */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** Cache of singleton factories: bean name to ObjectFactory. */
    /**
     * 三級快取  該map使用者快取key為beanName, value為objectFactory(包裝為早期物件)
     */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    /** Cache of early singleton objects: bean name to bean instance. */
    /**
     * 二級快取, 使用者快取我們的key為beanName, value是我們的早期物件(此時物件屬性還沒有...)
     */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

 

細細想來, 這三個快取都非常有存在的必要.

1.1 引入一級快取

剛開始, 只有一級快取, 在整個bean建立完成以後, 將其完整的bean放入到一級快取中. 這樣有什麼問題? 

1. bean建立一共有三大步驟, (例項化, 屬性賦值, 初始化) 等到整個過程都建立完, 在存入一級快取, 多執行緒怎麼辦? 第一個執行緒建立bean的過程中, 又來了一個執行緒, 他發現一級快取這時候還沒有, 就回去再次建立. 那不就重複了麼? ioc要求, bean是單例的.

2. 加鎖, 加鎖能否解決這個問題? 能, 但是效率超級低. 對一級快取加鎖, 那麼所有的物件建立過程中都要等待. 哪怕人家已經建立成功過. 效率太低, 不能接受

3. 於是就引入了二級快取. 

1.2 引入二級快取

二級快取的引入, 可以解決一級快取建立bean鏈路過長的問題,他在bean一旦被建立,立刻就放入到二級快取. 整個bean建立完成以後, 在放入到一級快取,刪除二級快取. 這樣做可以解決多執行緒建立bean的問題. 縮短了整個鏈路. 同時, 每次從快取中先獲取bean, 如果一級快取中已經有了,那麼直接返回. 不用在執行後面的建立程式碼

那麼,二級快取有什麼問題呢?

這就還需要知道一個問題, 那就是動態代理建立bean. 什麼時候, 去使用動態代理建立bean? 通常我們說在初始化之後, 呼叫bean的後置處理器建立bean. 這只是大多數bean建立動態代理的時候. 那如果有迴圈依賴呢? 有迴圈依賴, 還在初始化之後建立就晚了. 這是需要在例項化之後建立. 這樣,動態代理的程式碼就和建立bean耦合在一塊了. 違背單一性原則.

於是, 引入了三級快取

1.3 引入三級快取

三級快取的引入是為了解決耦合問題. 讓每一個方法只做一件事. 巧妙的使用了介面函式. 

 這個介面函式什麼用呢? 就相當於js中的回撥函式. 我在前面定義好, 但是不執行. 直到滿足條件了, 才執行. 這個方法, 可以大範圍應用到實踐工作中.

比如: 呼叫動態代理建立bean. 剛開始例項化完成以後, 我就賦予你這個能力, 你可以呼叫動態代理. 但是, 到後面, 你是否真的能夠運用這個能力呢? 不一定, 只有滿足條件, 才會運用這個能力. 

二. 定義介面函式, 也叫鉤子函式

在迴圈依賴原始碼中, 兩次使用到介面函式的方式. 

第一個是建立bean的時候. 第二個是三級快取

下面來看看原始碼,

第一次: 建立bean的時候, 定義了一個鉤子函式createBean()

sharedInstance = getSingleton(beanName, () -> {
    try {
        // 這裡定義了一個鉤子函式. 此時只是定義, 並不執行. 在真正需要建立bean的地方才會執行
        return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
        // Explicitly remove instance from singleton cache: It might have been put there
        // eagerly by the creation process, to allow for circular reference resolution.
        // Also remove any beans that received a temporary reference to the bean.
        destroySingleton(beanName);
        throw ex;
    }
});

實際上呼叫的時機是: 在getSingleton方法裡面. 回撥介面函式.

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            // 第一步: 從一級快取中獲取單例物件
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName,
                            "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                            "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                }
                // 第二步: 將bean新增到singletonsCurrentlyInCreation中, 表示bean正在建立
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<>();
                }
                try {
                    // 第三步: 這裡呼叫getObject()鉤子方法, 就會回撥匿名函式, 呼叫singletonFactory的createBean()
                    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) {
                    if (recordSuppressedExceptions) {
                        for (Exception suppressedException : this.suppressedExceptions) {
                            ex.addRelatedCause(suppressedException);
                        }
                    }
                    throw ex;
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

 

第二次呼叫: 是在三級快取定義的時候

呼叫addSingletonFactory(...)定義了一個鉤子函式. 這裡僅僅是定義, 並不執行

// 把我們的早期物件包裝成一個singletonFactory物件, 該物件提供了getObject()方法, 把靜態的bean放到三級快取中去了.
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

然後進入到addSingletonFactory內部, 只是把singletonFactory放入到了三級快取中, 這裡只是定義, 也並沒有執行

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);

                // 新增到已經註冊的singleton例項.
                this.registeredSingletons.add(beanName);
            }
        }
    }

什麼時候執行的呢? 再從快取中獲取物件的時候. 

@Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 從一級快取中獲取bean例項物件
        Object singletonObject = this.singletonObjects.get(beanName);
        /**
         * 如果在第一級的快取中沒有獲取到物件, 並且singletonsCurrentlyIncreation為true,也就是這個類正在建立.
         * 標明當前是一個迴圈依賴.
         *
         * 這裡有處理迴圈依賴的問題.-- 我們使用三級快取解決迴圈依賴
         */
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                /**
                 * 從二級快取中拿bean, 二級快取中的物件是一個早期物件
                 * 什麼是早期物件?就是bean剛剛呼叫了構造方法, 還沒有給bean的屬性進行賦值, 和初始化, 這就是早期物件
                  */

                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    /**
                     * 從三級快取拿bean, singletonFactories是用來解決迴圈依賴的關鍵所在.
                     * 在ios後期的過程中, 當bean呼叫了構造方法的時候, 把早期物件包裝成一個ObjectFactory物件,暴露在三級快取中
                      */
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        /**
                         * 在這裡通過暴露的ObjectFactory包裝物件. 通過呼叫他的getObject()方法來獲取物件
                         * 在這個環節中會呼叫getEarlyBeanReference()來進行後置處理
                         */
                        singletonObject = singletonFactory.getObject();
                        // 把早期物件放置在二級快取中
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 刪除三級快取
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

在這裡呼叫三級快取, singletonObject = singletonFactory.getObject(); 回撥鉤子函式. 

相關文章