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

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

本次部落格的目標

1. 手寫spring迴圈依賴的整個過程

2. spring怎麼解決迴圈依賴

3. 為什麼要二級快取和三級快取

4. spring有沒有解決建構函式的迴圈依賴

5. spring有沒有解決多例下的迴圈依賴.


一.  什麼是迴圈依賴?

如下圖所示: 

 

 A類依賴了B類, 同時B類有依賴了A類. 這就是迴圈依賴, 形成了一個閉環

 

 

如上圖: A依賴了B, B同時依賴了A和C , C依賴了A. 這也是迴圈依賴. , 形成了一個閉環

 

那麼, 如果出現迴圈依賴, spring是如何解決迴圈依賴問題的呢?

二. 模擬迴圈依賴

2.1 復現迴圈依賴

我們定義三個類:

1. 新增類InstanceA

package com.lxl.www.circulardependencies;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class InstanceA {
    @Autowired
    private InstanceB instanceB;

    public InstanceA() {
        System.out.println("呼叫 instanceA的建構函式");
    }

    public InstanceA(InstanceB instanceB) {
        this.instanceB = instanceB;
    }

    public void say(){
        System.out.println( "I am A");
    }


    public InstanceB getInstanceB() {
        return instanceB;
    }

    public void setInstanceB(InstanceB instanceB) {
        this.instanceB = instanceB;
    }

}

 

這是InstanceA, 裡面引用了InstanceB.

 

2. 新增類instanceB

package com.lxl.www.circulardependencies;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class InstanceB {

    @Autowired
    private InstanceA instanceA;
    public InstanceB() {
        System.out.println("呼叫 instanceB的建構函式");
    }

    public InstanceA getInstanceA() {
        return instanceA;
    }

    public void setInstanceA(InstanceA instanceA) {
        this.instanceA = instanceA;
    }



}

 

這是InstanceB, 在裡面有引用了InstanceA

3:模擬spring是如何建立Bean的

這個在前面已經說過了, 首先會載入配置類的後置處理器, 將其解析後放入到beanDefinitionMap中. 然後載入配置類, 也將其解析後放入beanDefinitionMap中. 最後解析配置類. 我們這裡直接簡化掉前兩步, 將兩個類放入beanDefinitionMap中. 主要模擬第三步解析配置類. 在解析的過程中, 獲取bean的時候會出現迴圈依賴的問題迴圈依賴.

第一步: 將兩個類放入到beanDefinitionMap中

public class MainStart {
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

/*
* * 讀取bean定義, 當然在spring中肯定是根據配置 動態掃描註冊的 * * InstanceA和InstanceB都有註解@Component, 所以, 在spring掃描讀取配置類的時候, 會把他們兩個掃描到BeanDefinitionMap中. * 這裡, 我們省略這一步, 直接將instanceA和instanceB放到BeanDefinitionMap中. */ public static void loadBeanDefinitions(){ RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class); RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class); beanDefinitionMap.put("instanceA", aBeanDefinition); beanDefinitionMap.put("instanceB", bBeanDefinition); } public static void main(String[] args) throws Exception { // 第一步: 掃描配置類, 讀取bean定義 loadBeanDefinitions(); ...... }

 

上面的程式碼結構很簡單, 再看一下注釋應該就能明白了. 這裡就是模擬spring將配置類解析放入到beanDefinitionMap的過程. 

 

第二步: 迴圈建立bean

首先,我們已經知道, 建立bean一共有三個步驟: 例項化, 屬性賦值, 初始化. 

 

 

 

而在屬性賦值的時候, 會判斷是否引用了其他的Bean, 如果引用了, 那麼需要構建此Bean. 下面來看一下程式碼

/**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {/**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object instanceBean = beanClass.newInstance();

       /**
         *  第二步: 屬性賦值
         *  instanceA這類類裡面有一個屬性, InstanceB. 所以, 先拿到 instanceB, 然後在判斷屬性頭上有沒有Autowired註解.
         *  注意: 這裡我們只是判斷有沒有Autowired註解. spring中還會判斷有沒有@Resource註解. @Resource註解還有兩種方式, 一種是name, 一種是type
         */
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField: declaredFields) {
            // 判斷每一個屬性是否有@Autowired註解
            Autowired annotation = declaredField.getAnnotation(Autowired.class);
            if (annotation != null) {
                // 設定這個屬性是可訪問的
                declaredField.setAccessible(true);
                // 那麼這個時候還要構建這個屬性的bean.
                /*
                 * 獲取屬性的名字
                 * 真實情況, spring這裡會判斷, 是根據名字, 還是型別, 還是建構函式來獲取類.
                 * 我們這裡模擬, 所以簡單一些, 直接根據名字獲取.
                 */
                String name = declaredField.getName();

                /**
                 * 這樣, 在這裡我們就拿到了 instanceB 的 bean
                 */
                Object fileObject = getBean(name);

                // 為屬性設定型別
                declaredField.set(instanceBean, fileObject);
            }
        }


        /**
         * 第三步: 初始化
         * 初始化就是設定類的init-method.這個可以設定也可以不設定. 我們這裡就不設定了
         */
        return instanceBean;
    }

 

我們看到如上程式碼. 

第一步: 例項化: 使用反射的方式, 根據beanName查詢構建一個例項bean. 

第二步: 屬性賦值: 判斷屬性中是否有@Autowired屬性, 如果有這個屬性, 那麼需要構建bean. 我們發現在為InstanceA賦值的時候, 裡面引用了InstanceB, 所以去建立InstanceB, 而建立InstanceB的時候, 發現裡面又有InstanceA, 於是又去建立A. 然後以此類推,繼續判斷. 就形成了死迴圈. 無法走出這個環. 這就是迴圈依賴

第三步: 初始化: 呼叫init-method, 這個方法不是必須有, 所以,我們這裡不模擬了

看看如下圖所示

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

 

 紅色部分就形成了迴圈依賴.

4: 增加一級快取, 解決迴圈依賴的問題. 

我們知道上面進行了迴圈依賴了. 其實, 我們的目標很簡單, 如果一個類建立過了, 那麼就請不要在建立了. 

所以, 我們增加一級快取

  // 一級快取
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

   /**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {

        // 增加一個出口. 判斷實體類是否已經被載入過了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }

        /**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object instanceBean = beanClass.newInstance();

        /**
         * 第二步: 放入到一級快取
         */
        singletonObjects.put(beanName, instanceBean);
        /**
         *  第三步: 屬性賦值
         *  instanceA這類類裡面有一個屬性, InstanceB. 所以, 先拿到 instanceB, 然後在判斷屬性頭上有沒有Autowired註解.
         *  注意: 這裡我們只是判斷有沒有Autowired註解. spring中還會判斷有沒有@Resource註解. @Resource註解還有兩種方式, 一種是name, 一種是type
          */
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField: declaredFields) {
            // 判斷每一個屬性是否有@Autowired註解
            Autowired annotation = declaredField.getAnnotation(Autowired.class);
            if (annotation != null) {
                // 設定這個屬性是可訪問的
                declaredField.setAccessible(true);
                // 那麼這個時候還要構建這個屬性的bean.
                /*
                 * 獲取屬性的名字
                 * 真實情況, spring這裡會判斷, 是根據名字, 還是型別, 還是建構函式來獲取類.
                 * 我們這裡模擬, 所以簡單一些, 直接根據名字獲取.
                 */
                String name = declaredField.getName();

                /**
                 * 這樣, 在這裡我們就拿到了 instanceB 的 bean
                 */
                Object fileObject = getBean(name);

                // 為屬性設定型別
                declaredField.set(instanceBean, fileObject);
            }
        }

        /**
         * 第四步: 初始化
         * 初始化就是設定類的init-method.這個可以設定也可以不設定. 我們這裡就不設定了
         */
        return instanceBean;
    }

 

還是上面的獲取bean的流程, 不一樣的是, 這裡增加了以及快取. 當我們獲取到bean例項以後, 將其放入到快取中. 下次再需要建立之前, 先去快取裡判斷,是否已經有了, 如果沒有, 那麼再建立. 

這樣就給建立bean增加了一個出口. 不會迴圈建立了.

 

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

如上圖所示, 在@Autowired的時候, 增加了一個出口. 判斷即將要建立的類是否已經存在, 如果存在了, 那麼就直接返回, 不在建立

雖然使用了一級快取解決了迴圈依賴的問題, 但要是在多執行緒下, 這個依賴可能就會出現問題.

比如: 有兩個執行緒, 同時建立instanceA 和instanceB, instanceA和instanceB都引用了instanceC. 他們同步進行, 都去建立instanceC. 首先A去建立, A在例項化instanceC以後就將其放入到一級快取了, 這時候, B去一級快取裡拿. 此時拿到的instanceC是不完整的. 後面的屬性賦值, 初始化都還沒有執行呢. 所以, 我們增加耳機快取來解決這個問題. 

 

5. 增加二級快取, 區分完整的bean和純淨的bean.

public class MainStart {
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    // 一級快取
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 二級快取
    private static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    /**
     * 讀取bean定義, 當然在spring中肯定是根據配置 動態掃描註冊的
     *
     * InstanceA和InstanceB都有註解@Component, 所以, 在spring掃描讀取配置類的時候, 會把他們兩個掃描到BeanDefinitionMap中.
     * 這裡, 我們省略這一步, 直接將instanceA和instanceB放到BeanDefinitionMap中.
     */
    public static void loadBeanDefinitions(){
        RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
        RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
        beanDefinitionMap.put("instanceA", aBeanDefinition);
        beanDefinitionMap.put("instanceB", bBeanDefinition);
    }

    public static void main(String[] args) throws Exception {

        // 第一步: 掃描配置類, 讀取bean定義
        loadBeanDefinitions();

        // 第二步: 迴圈建立bean
        for (String key: beanDefinitionMap.keySet()) {
            // 第一次: key是instanceA, 所以先建立A類
            getBean(key);

        }

        // 測試: 看是否能執行成功
        InstanceA instanceA = (InstanceA) getBean("instanceA");
        instanceA.say();

    }

    /**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {

        // 增加一個出口. 判斷實體類是否已經被載入過了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }

        /**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object instanceBean = beanClass.newInstance();
        /**
         * 第二步: 放入到二級快取
         */
        earlySingletonObjects.put(beanName, instanceBean);

        /**
         *  第三步: 屬性賦值
         *  instanceA這類類裡面有一個屬性, InstanceB. 所以, 先拿到 instanceB, 然後在判斷屬性頭上有沒有Autowired註解.
         *  注意: 這裡我們只是判斷有沒有Autowired註解. spring中還會判斷有沒有@Resource註解. @Resource註解還有兩種方式, 一種是name, 一種是type
          */
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField: declaredFields) {
            // 判斷每一個屬性是否有@Autowired註解
            Autowired annotation = declaredField.getAnnotation(Autowired.class);
            if (annotation != null) {
                // 設定這個屬性是可訪問的
                declaredField.setAccessible(true);
                // 那麼這個時候還要構建這個屬性的bean.
                /*
                 * 獲取屬性的名字
                 * 真實情況, spring這裡會判斷, 是根據名字, 還是型別, 還是建構函式來獲取類.
                 * 我們這裡模擬, 所以簡單一些, 直接根據名字獲取.
                 */
                String name = declaredField.getName();

                /**
                 * 這樣, 在這裡我們就拿到了 instanceB 的 bean
                 */
                Object fileObject = getBean(name);

                // 為屬性設定型別
                declaredField.set(instanceBean, fileObject);
            }
        }


        /**
         * 第四步: 初始化
         * 初始化就是設定類的init-method.這個可以設定也可以不設定. 我們這裡就不設定了
         */

        /**
         * 第二步: 放入到一級快取
         */
        singletonObjects.put(beanName, instanceBean);
        return instanceBean;
    }

    /**
     * 判斷是否是迴圈引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        // 先去一級快取裡拿,如果一級快取沒有拿到,去二級快取裡拿
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        } else if (earlySingletonObjects.containsKey(beanName)){
            return earlySingletonObjects.get(beanName);
        } else {
            return null;
        }
    }
}

 

如上圖所示,增加了一個二級快取. 首先, 構建出instanceBean以後, 直接將其放入到二級快取中. 這時只是一個純淨的bean, 裡面還沒有給屬性賦值, 初始化. 在給屬性賦值完成, 初始化完成以後, 在將其放入到一級快取中. 

我們判斷快取中是否有某個例項bean的時候, 先去一級快取中判斷是否有完整的bean, 如果沒有, 就去二級快取中判斷有沒有例項化過這個bean. 

 總結: 一級快取和二級快取的作用

 

一級快取: 解決迴圈依賴的問題

二級快取: 在建立例項bean和放入到一級快取之間還有一段間隙. 如果在這之間從一級快取拿例項, 肯定是返回null的. 為了避免這個問題, 增加了二級快取.

 

我們都知道spring中有一級快取, 二級快取, 三級快取. 一級快取和二級快取的作用我們知道了, 那麼三級快取有什麼用呢?

 6. 增加三級快取

 三級快取有什麼作用呢? 這個問題眾說紛紜, 有說代理, 有說AOP. 其實AOP的問題可以用二級快取來解決. 下面就來看看AOP如何用二級快取解決.

建立AOP動態代理 (不是耦合的, 採用解耦的, 通過BeanPostProcessor bean的後置處理器來建立). 之前講過, 如下圖

在初始化之後, 呼叫Bean的後置處理器去建立的AOP的動態代理

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

 

 如上圖. 我們在建立bean 的時候, 會有很多Bean的後置處理器BeanPostProcessor. 如果有AOP, 會在什麼時候建立呢? 在初始化以後, 呼叫BeanPostProcessor建立動態代理. 

結合上面的程式碼, 我們想一想, 其實在初始化以後建立動態代理就晚了. 為什麼呢? 因為, 如果有迴圈依賴, 在初始化之後才呼叫, 那就不是動態代理. 其實我們這時候應該在例項化之後, 放入到二級快取之前呼叫

面試題: 在建立bean的時候, 在哪裡建立的動態代理, 這個應該怎麼回答呢?
很多人會說在初始化之後, 或者在例項化之後.
其實更嚴謹的說, 有兩種情況: 第一種是在初始化之後呼叫 . 第二種是出現了迴圈依賴, 會在例項化之後呼叫

 我們上面說的就是第二種情況. 也就是說,正常情況下是在初始化之後呼叫的, 但是如果有迴圈依賴, 就要在例項化之後呼叫了.

 

下面來看看如何在二級快取加動態代理. 

首先, 我們這裡有迴圈依賴, 所以將動態代理放在例項化之後, 

  /**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {

        // 增加一個出口. 判斷實體類是否已經被載入過了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }

        /**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object instanceBean = beanClass.newInstance();

        /**
         * 建立AOP動態代理 (不是耦合的, 採用解耦的, 通過BeanPostProcessor bean的後置處理器得來的.  之前講過,
         * 在初始化之後, 呼叫Bean的後置處理器去建立的AOP的動態代理 )
         */
        instanceBean = new JdkProxyBeanPostProcessor().getEarlyBeanReference(instanceBean, "instanceA");

        /**
         * 第二步: 放入到二級快取
         */
        earlySingletonObjects.put(beanName, instanceBean);

        /**
         *  第三步: 屬性賦值
         *  instanceA這類類裡面有一個屬性, InstanceB. 所以, 先拿到 instanceB, 然後在判斷屬性頭上有沒有Autowired註解.
         *  注意: 這裡我們只是判斷有沒有Autowired註解. spring中還會判斷有沒有@Resource註解. @Resource註解還有兩種方式, 一種是name, 一種是type
          */
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField: declaredFields) {
            // 判斷每一個屬性是否有@Autowired註解
            Autowired annotation = declaredField.getAnnotation(Autowired.class);
            if (annotation != null) {
                // 設定這個屬性是可訪問的
                declaredField.setAccessible(true);
                // 那麼這個時候還要構建這個屬性的bean.
                /*
                 * 獲取屬性的名字
                 * 真實情況, spring這裡會判斷, 是根據名字, 還是型別, 還是建構函式來獲取類.
                 * 我們這裡模擬, 所以簡單一些, 直接根據名字獲取.
                 */
                String name = declaredField.getName();

                /**
                 * 這樣, 在這裡我們就拿到了 instanceB 的 bean
                 */
                Object fileObject = getBean(name);

                // 為屬性設定型別
                declaredField.set(instanceBean, fileObject);
            }
        }


        /**
         * 第四步: 初始化
         * 初始化就是設定類的init-method.這個可以設定也可以不設定. 我們這裡就不設定了
         */
      //   正常動態代理建立的時機
        
        /**
         * 第五步: 放入到一級快取
         */
        singletonObjects.put(beanName, instanceBean);
        return instanceBean;
    }

 

這裡只是簡單模擬了動態代理.

我們知道動態代理有兩個地方. 如果是普通類動態代理在初始化之後執行, 如果是迴圈依賴, 那麼動態代理是在例項化之後. 

 

上面在例項化之後建立proxy的程式碼不完整, 為什麼不完整呢, 因為沒有判斷是否是迴圈依賴. 

 

我們簡單模擬一個動態代理的實現.

public class JdkProxyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
    /**
     * 假設A被切點命中 需要建立代理  @PointCut("execution(* *..InstanceA.*(..))")
     * @param bean the raw bean instance
     * @param beanName the name of the bean
     * @return
     * @throws BeansException
     */
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {

        // 假設A被切點命中 需要建立代理  @PointCut("execution(* *..InstanceA.*(..))")

        /**
         * 這裡, 我們簡單直接判斷bean是不是InstanceA例項, 如果是, 就建立動態代理.
         * 這裡沒有去解析切點, 解析切點是AspectJ做的事.
         */
        if (bean instanceof InstanceA) {
            JdkDynimcProxy jdkDynimcProxy = new JdkDynimcProxy(bean);
            return jdkDynimcProxy.getProxy();
        }
        return bean;
    }
}

 

這裡直接判斷, 如果bean是InstanceA的例項, 那麼就呼叫bean的動態代理.  動態代理的簡單邏輯就是: 解析切面, 然後建立類, 如果類不存在就新增, 如果存在則不在建立, 直接取出來返回.

 

在來看看動態代理,放在例項化之後. 建立AOP, 但是, 在這裡建立AOP動態代理的條件是迴圈依賴.

問題1: 那麼如何判斷是迴圈依賴呢?

二級快取中bean不是null. 

如果一個類在建立的過程中, 會放入到二級快取, 如果完全建立完了, 會放入到一級快取, 然後刪除二級快取. 所以, 如果二級快取中的bean只要存在, 就說明這個類是建立中, 出現了迴圈依賴.

問題2: 什麼時候判斷呢?

應該在getSingleton()判斷是否是迴圈依賴的時候判斷. 因為這時候我們剛好判斷了二級快取中bean是否為空.

/**
     * 判斷是否是迴圈引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        // 先去一級快取裡拿,如果一級快取沒有拿到,去二級快取裡拿
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        } else if (earlySingletonObjects.containsKey(beanName)){

            /**
             * 第一次建立bean是正常的instanceBean. 他並不是迴圈依賴. 第二次進來判斷, 這個bean已經存在了, 就說明是迴圈依賴了
             * 這時候通過動態代理建立bean. 然後將這個bean在放入到二級快取中覆蓋原來的instanceBean.
             */
            Object obj = new JdkProxyBeanPostProcessor()
                    .getEarlyBeanReference(earlySingletonObjects.get(beanName), beanName);

            earlySingletonObjects.put(beanName, obj);

            return earlySingletonObjects.get(beanName);
        } else {
            return null;
        }
    }

 

這樣我們在迴圈依賴的時候就完成了AOP的建立. 這是在二級快取裡建立的AOP,

問題3: 那這是不是說就不需要三級快取了呢?

那麼,來找問題.  這裡有兩個問題:

問題1: 我們發現在建立動態代理的時候, 我們使用的bean的後置處理器JdkProxyBeanPostProcessor.這有點不太符合規則,
      因為, spring在getBean()的時候並沒有使用Bean的後置處理器, 而是在createBean()的時候才去使用的bean的後置處理器.
問題2: 如果A是AOP, 他一直都是, 最開始建立的時候也應該是. 使用這種方法, 結果是第一次建立出來的bean不是AOP動態代理.

 

對於第一個問題: 我們希望在例項化的時候建立AOP, 但是具體判斷是在getSingleton()方法裡判斷. 這裡通過三級快取來實現. 三級快取裡面放的是一個介面定義的鉤子方法. 方法的執行在後面呼叫的時候執行. 

 

對於第二個問題: 我們的二級快取就不能直接儲存instanceBean例項了, 增加一個引數, 用來標記當前這個類是一個正在建立中的類. 這樣來判斷迴圈依賴.

 

下面先來看看建立的三個快取和一個標識

  // 一級快取
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 二級快取: 為了將成熟的bean和純淨的bean分離. 避免讀取到不完整的bean.
    private static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    // 三級快取:
    private static Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();

    // 迴圈依賴的標識---當前正在建立的例項bean
    private static Set<String> singletonsCurrectlyInCreation = new HashSet<>();

 

然後在來看看迴圈依賴的出口

/**
     * 判斷是否是迴圈引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        //先去一級快取裡拿
        Object bean = singletonObjects.get(beanName);
        // 一級快取中沒有, 但是正在建立的bean標識中有, 說明是迴圈依賴
        if (bean == null && singletonsCurrectlyInCreation.contains(beanName)) {
            bean = earlySingletonObjects.get(beanName);
            // 如果二級快取中沒有, 就從三級快取中拿
            if (bean == null) {
                // 從三級快取中取
                ObjectFactory objectFactory = singletonFactories.get(beanName);
                if (objectFactory != null) {
                    // 這裡是真正建立動態代理的地方.
                    Object obj = objectFactory.getObject();
                    // 然後將其放入到二級快取中. 因為如果有多次依賴, 就去二級快取中判斷. 已經有了就不在再次建立了
                    earlySingletonObjects.put(beanName, obj);
                }
            }
        }
        return bean;
    }

 

這裡的邏輯是, 先去一級快取中拿, 一級快取放的是成熟的bean, 也就是他已經完成了屬性賦值和初始化. 如果一級快取沒有, 而正在建立中的類標識是true, 就說明這個類正在建立中, 這是一個迴圈依賴. 這個時候就去二級快取中取資料, 二級快取中的資料是何時放進去的呢, 是後面從三級快取中建立動態代理後放進去的. 如果二級快取為空, 說明沒有建立過動態代理, 這時候在去三級快取中拿, 然後建立動態代理. 建立完以後放入二級快取中, 後面就不用再建立. 

 

完成的程式碼如下:

package com.lxl.www.circulardependencies;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class MainStart {
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    // 一級快取
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 二級快取: 為了將成熟的bean和純淨的bean分離. 避免讀取到不完整的bean.
    private static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    // 三級快取:
    private static Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();

    // 迴圈依賴的標識---當前正在建立的例項bean
    private static Set<String> singletonsCurrectlyInCreation = new HashSet<>();

    /**
     * 讀取bean定義, 當然在spring中肯定是根據配置 動態掃描註冊的
     *
     * InstanceA和InstanceB都有註解@Component, 所以, 在spring掃描讀取配置類的時候, 會把他們兩個掃描到BeanDefinitionMap中.
     * 這裡, 我們省略這一步, 直接將instanceA和instanceB放到BeanDefinitionMap中.
     */
    public static void loadBeanDefinitions(){
        RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
        RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
        beanDefinitionMap.put("instanceA", aBeanDefinition);
        beanDefinitionMap.put("instanceB", bBeanDefinition);
    }

    public static void main(String[] args) throws Exception {

        // 第一步: 掃描配置類, 讀取bean定義
        loadBeanDefinitions();

        // 第二步: 迴圈建立bean
        for (String key: beanDefinitionMap.keySet()) {
            // 第一次: key是instanceA, 所以先建立A類
            getBean(key);

        }

        // 測試: 看是否能執行成功
        InstanceA instanceA = (InstanceA) getBean("instanceA");
        instanceA.say();

    }

    /**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {

        // 增加一個出口. 判斷實體類是否已經被載入過了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }

        // 標記bean正在建立
        if (!singletonsCurrectlyInCreation.contains(beanName)) {
            singletonsCurrectlyInCreation.add(beanName);
        }

        /**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object instanceBean = beanClass.newInstance();


        /**
         * 第二步: 放入到三級快取
         * 每一次createBean都會將其放入到三級快取中. getObject是一個鉤子方法. 在這裡不會被呼叫.
         * 什麼時候被呼叫呢?
         * 在getSingleton()從三級快取中取資料, 呼叫建立動態代理的時候
         */
        singletonFactories.put(beanName, new ObjectFactory() {
            @Override
            public Object getObject() throws BeansException {
                return new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletonObjects.get(beanName), beanName);
            }
        });
        //earlySingletonObjects.put(beanName, instanceBean);

        /**
         *  第三步: 屬性賦值
         *  instanceA這類類裡面有一個屬性, InstanceB. 所以, 先拿到 instanceB, 然後在判斷屬性頭上有沒有Autowired註解.
         *  注意: 這裡我們只是判斷有沒有Autowired註解. spring中還會判斷有沒有@Resource註解. @Resource註解還有兩種方式, 一種是name, 一種是type
         */
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField: declaredFields) {
            // 判斷每一個屬性是否有@Autowired註解
            Autowired annotation = declaredField.getAnnotation(Autowired.class);
            if (annotation != null) {
                // 設定這個屬性是可訪問的
                declaredField.setAccessible(true);
                // 那麼這個時候還要構建這個屬性的bean.
                /*
                 * 獲取屬性的名字
                 * 真實情況, spring這裡會判斷, 是根據名字, 還是型別, 還是建構函式來獲取類.
                 * 我們這裡模擬, 所以簡單一些, 直接根據名字獲取.
                 */
                String name = declaredField.getName();

                /**
                 * 這樣, 在這裡我們就拿到了 instanceB 的 bean
                 */
                Object fileObject = getBean(name);

                // 為屬性設定型別
                declaredField.set(instanceBean, fileObject);
            }
        }


        /**
         * 第四步: 初始化
         * 初始化就是設定類的init-method.這個可以設定也可以不設定. 我們這裡就不設定了
         */


        /**
         * 第五步: 放入到一級快取
         *
         * 在這裡二級快取存的是動態代理, 那麼一級快取肯定也要存動態代理的例項.
         * 從二級快取中取出例項, 放入到一級快取中
         */
        if (earlySingletonObjects.containsKey(beanName)) {
            instanceBean = earlySingletonObjects.get(beanName);
        }
        singletonObjects.put(beanName, instanceBean);
        return instanceBean;
    }

    /**
     * 判斷是否是迴圈引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        //先去一級快取裡拿,
        Object bean = singletonObjects.get(beanName);
        // 一級快取中沒有, 但是正在建立的bean標識中有, 說明是迴圈依賴
        if (bean == null && singletonsCurrectlyInCreation.contains(beanName)) {
            bean = earlySingletonObjects.get(beanName);
            // 如果二級快取中沒有, 就從三級快取中拿
            if (bean == null) {
                // 從三級快取中取
                ObjectFactory objectFactory = singletonFactories.get(beanName);
                if (objectFactory != null) {
                    // 這裡是真正建立動態代理的地方.
                    Object obj = objectFactory.getObject();
                    // 然後將其放入到二級快取中. 因為如果有多次依賴, 就去二級快取中判斷. 已經有了就不在再次建立了
                    earlySingletonObjects.put(beanName, obj);
                }
            }
        }
        return bean;
    }
}

 

相關文章