1. 程式人生 > >探究Dubbo的拓展機制: 下

探究Dubbo的拓展機制: 下

承接上篇, 本篇博文的主題就是認認真真捋一捋, 看一下 Dubbo是如何實現他的IOC / AOP / 以及Dubbo SPI這個拓展點的

總覽:

本篇的話總體上分成兩部分進行展開

  • 第一點就是 Dubbo在啟動過程中載入原生的配置檔案中提供的被@SPI標記的實現類:

  • 第二就是Dubbo載入程式設計師後續新增進去的被@SPI標註的介面和實現類, 進而探究 Dubbo的IOC / AOP / 以及Dubbo SPI這個拓展點機制

環境的初始化

入口程式

如下程式碼是追蹤的起點:

我也是看了好多遍才勉強將這個過程整理明白一些, 但是根據以往的經驗來說, 過一倆月之後我可能就會淡忘這個流程... 為了讓自己一段時間後快速的回憶起來這個流程, 所以我要對自己說下面一段話

Dubbo的拓展點編碼實現中, 會反反覆覆的出現下面這段程式碼

ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(XXX.class); extensionLoader.getExtension("XXX");

先說這段程式碼在幹什麼? 其實上它就是在為 Dubbo原生的SPI介面, 或者是使用者提供的SPI介面 結合SPI的配置檔案中的配置, 找到這些SPI介面的實現類, 並且這個過程中穿插Dubbo的IOC已經AOP機制

不得不服氣, 這一段程式碼的實現, 因為這段程式碼設計不僅僅能載入Dubbo提供的原生的SPI介面, 也能載入使用 使用者自定義的SPI , 詳細的過程在下文中展開, 妙!!!

明星類 ExtensionLoader.java

應該得, 隆重的介紹一下這個明星類 ExtensionLoader.java

從名字上看, ExtensionLoader , 見名知意, 拓展點的載入器, 那什麼是Dubbo的拓展點呢? 拓展點就是Dubbo允許使用者參與到Dubbo環境的初始化這個過程中來, 允許使用者定製Dubbo行為, 諸如 Dubbo的 SPI / IOC / AOP (上一篇博文主要的學習內容就是Dubbo的拓展點的使用)

見名知意: ExtensionLoader 拓展點的載入器, 就是使用這個封裝類, 我們可以載入Dubbo提供的拓展點, 說白了, 其實載入為SPI介面找到實現類, 以及完成這些實現類之間的 AOP增強 + IOC 依賴注入的過程

此外這個類很有必要看, 為啥呢? 第一點就是說它的設計很巧妙, 程式碼的抽象和複用能力都很好, 第二點就是說, 我們可以一睹大神們的風采, 如果 實現自己的SPI , 如何實現自己的IOC AOP

入口方法

下面就是入口程式中的第一個方法, getExtensionLoader(Class<T> type) 很簡單, 就是根據型別找到對應的 ExtensionLoader, 待會Dubbo就會為我新增進去SPI介面生成這樣的 ExtensionLoader : org.apache.dubbo.common.extension.ExtensionLoader[com.changwu.ioc.api.PersonInterface]

當然Dubbo也有自己原生的ExtensionLoader

從我的入口程式來看, 很顯然, 我傳遞進來的 type = PersonInterface , 方法執行的邏輯如下

  • 對type引數進行校驗
  • 檢查快取中是否存在 PersonInterface的 ExtensionLoader
    • 如果有的話, 返回這個現存的ExtensionLoader
    • 如果不存在就建立一個新的
 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }

        //todo 這裡體現了快取機制, EXTENSION_LOADERS 其實就是 CurrentHashMap
        //todo EXTENSION_LOADERS  是 CurrentHashMap , 每一種interfaceType 都對應一個 ExtensionLoader , 但是這些 ExtensionLoader全部被維護在 這個EXTENSION_LOADER中
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

ExtensionLoader蹊蹺的構造方法

那我們是第一次進來, 肯定是沒有的, 因此我們看他是如何進行new ExtensionLoader<T>(type)的, 所以跟進看一下它的構造方法

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        // 對於一個介面,比如PersonInterface介面,有兩種實現類,一種就是我們自定義的實現類,比如Student,還有一種就是代理類,對於代理類,可以由我們自己實現,也可以讓Dubbo幫我們實現,而代理類主要就是依賴注入時使用
        // todo ExtensionFactory 是dubbo的拓展機制工廠, 它裡面封裝了 Dubbo的SPI拓展機制和Spring的拓展機制
        // todo ExtensionLoader.getExtensionLoader(ExtensionFactory.class) ===>  獲取  自適應的extension
        //  ||          ||          ||             ||               ||          ||
        // todo ExtensionLoader.getExtensionLoader(PersonInterface.class)  ===>  昌武, 你獲取的是:  extension("human")
        // todo 你看這是不是挺清晰的
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

在上面的構造方法中, 就有蹊蹺了, 邏輯如下

  • 將type = PersonInterface 儲存起來
  • 獲取 ExtensionFactory.class 型別的 ExtensionLoader
  • 獲取 ExtensionFactory.class 型別的 ExtensionLoader的自適應的 Extension

我所說的有蹊蹺的地方: 我們本來不是前來建立PersionInterface 的ExtensionLoader嗎? 怎麼先建立 ExtensionFactory的 ExtensionLoader呢?

(因為在建立ExtensionFactory的 ExtensionLoader的過程中會去載入Dubbo提供的其他的諸如SpiExtensionFactory這一類的實現, 這些預設的實現的作用就是輔助Dubbo再去解析使用者提供的SPI實現體系)

下面看看這個 ExtensionFactory.class類

沒錯! 它被新增上了@SPI的註解, 說明和 我們的PersonInterface一樣, 是DubboSPI

那好吧, 既然Dubbo想先完成它的例項化, 就往下看, 我在博文開頭就不停的說, Dubbo設計的很好, 這裡不就遞迴呼叫getExtensionLoader(type= ExtensionFactory.class)了嗎? 不出意外的話, 再一次的 進去構造方法, 然後在這個三元判斷表示式中發現了 type == ExtensionFactory.class ? null : ExtensionLoader.getE... 前半部分是滿足條件的, 然後設定objectFactory=null, 完成 ExtensionFactory的構造, 然後執行getAdaptiveExtension()

獲取自適應的Extension

這個 getAdaptiveExtension() 同樣需要好好的看看, 見名知意, 返回一個自適應的 Extension, 說白了就是返回Dubbo通過字元拼接出來的Extension類

下面看看這個 getAdaptiveExtension() 原始碼如下:

    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                // todo 為了執行緒安全 , 使用了雙重同步鎖
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // todo 跟進去
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

著重跟進 instance = createAdaptiveExtension();方法, 原始碼如下: 主要邏輯如下:

  • 獲取出 AdaptiveExtensionClass
    • 啟動過程中, 第一次獲取到的是 Dubbo提供的預設實現類, 叫 AdaptiveExtensionFactory
    • 第二次獲取到的是 Dubbo為使用者提供的SPI介面動態生成的實現類
  • 例項化AdaptiveExtensionClass
  • 對例項化的AdaptiveExtensionClass 進行依賴注入的操作

載入SPI配置檔案, 獲取所有的ExtensionClass

ExtensionClass 可以直白的理解成 SPI 介面的實現類, 或者是wrapper類

上面的程式碼中想要獲取一個 AdaptiveExtensionClass() 那麼問題來了, 從哪裡獲取呢? 跟進getAdaptiveExtensionClass()

沒錯就在下面的

    private Class<?> getAdaptiveExtensionClass() {
        // todo 載入配置檔案
        getExtensionClasses();
        // todo 在前一步載入配置檔案時, 載入到了 AdaptiveExtensionFactory, 這裡返回的就是 CachedAdaptiveClass
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 如果沒有手動實現介面的代理類,那麼Dubbo就會自動給你實現一個
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

往下跟進getExtensionClasses();

下面的函式中維護著一個 cachedClasses 它是一個Map , key=String value= Class ; 說白了, 存放的就是從SPI配置檔案中讀取配置資訊

  // 實際上就是將配置中的 key=value 讀取裝在進map中
    private Map<String, Class<?>> getExtensionClasses() {
        // todo  cachedClasses是 ExtensionLoader的屬性: Holder<Map<String, Class<?>>> cachedClasses
        // todo  用於儲存提前約定好了儲存在 類路徑下的  METE-INF/services  以及dubbo原生提供的擴充套件點
        // todo  同樣是雙重同步鎖 + volatile  保證執行緒安全
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // todo 著重看這個函式
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

進行跟進loadExtensionClasses();

可以看到, Dubbo會按照約定讀取下面幾個配置檔案中的配置資訊, 下面我註釋上的檔案的全路徑所對應的檔案中會記錄Dubbo原生的SPI的實現, 我們也能遵循這個規則提供自己的實現類

比如隨便檢視一個配置檔案

    // synchronized in getExtensionClasses
    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        // todo 跟進這個 loadDirectory() 方法, 看看
        // todo    META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());

        // todo    META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));

        // todo    META-INF/dubbo/org.apache.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());

        //todo     META-INF/dubbo/com.alibaba.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));

        //todo     META-INF/services/org.apache.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());

        //todo     META-INF/services/com.alibaba.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

下面看一下處理的詳細細節資訊:

  • 如果從配置檔案中讀取到的 SPI的實現類添加了@Adaptive註解, 就先快取起來
    • Dubbo將 AdaptiveExtensionFactory.java暫時快取起來了
  • 沒新增@Adaptive的話, 同樣將其快取在不同額容器中, 稍後使用
    • Dubbo建立的例項物件是: SpiExtensionFactory,java
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        // todo 檢視有沒有標註 @Adaptive 註解, 如果標註有這個註解的話, 那麼就將他暫時存放起來, 而不是執行下面的邏輯, 構造出物件來
        // todo 昌武, 你看, 你在驗證ioc時, 你提供的PersonInterface很顯然是存在這個@Adaptive註解 ,他會在上面提到getAdaptiveClasss() 後 然後newInstance()建立例項

        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, name);
                }
            }
        }
    }

小結: 到這裡基本上就到了Dubbo的底層確實會去讀取配置檔案, 根據他們的配置情況, 快取在不同容器中

好, 到這裡上面所說的getExtensionClasses(); 方法就說完了, 回到下面的方法中

得到了AdaptiveExtensionFactory類之後, 接著就通過反射建立的它例項物件, 所以說, 我們要去看他的構造方法, 如下:

  • AdaptiveExtensionFactory繼承了 ExtensionFactory, 因此它需要重寫 getExtension(Class<T> type, String name)
  • 重點看他的構造方法

又看到了這行程式碼ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); 這行程式碼的執行流程其實已經說過了, 這次根據名稱獲取的 Extension是 SpiExtensionFactory, 並將它維護起來

新的問題就來了, 這個SPIExtensionFactory是誰呢? 有啥用呢 看下面, 說白了, 用它處理新增有Dubbo的SPI註解的介面, 然後嘗試獲取這些介面的 實現

構建方法執行完成了, 也就說明 AdaptiveExtension 建立完成了, 剛才所說的 createAdaptiveExtension

injectExtension其實就是回去做IOC / AOP 相關的操作, 現在我們跟蹤的實現類是 AdaptiveExtension 它沒有依賴其他的屬性, 但是我提供的PersonInterface依賴了, 所以說我們暫時先不進如這個方法,稍後再進去檢視他的實現

小結: 下圖是我們的啟動類, 到目前為止, 我們就看完了啟動類的第一行程式碼做了什麼, 那它主要是做了哪些事情呢?

  • 例項化 : AdaptiveExtensionFactory
  • 例項化 : SPIExtensionFactory
    • 可用來處理使用者後續新增進來的SPI相關邏輯
  • 例項化 : 使用者提供的Spi介面的 ExtensionLoader

Dubbo的IOC細節

下面就繼續看這行extensionLoader.getExtension("human") , 看他的返回值, 很明顯, 就是要返回我們需要的personInterface的實現類, 並在這個過程中穿插這IOC和AOP的邏輯

回顧一下實驗的環境, 重新整理一下思路: 我們想獲取出 key = human的 PersonInterface的實現類, 這實現類長下面這樣:

public class Human implements PersonInterface {

    private PersonInterface personInterface;

    //todo 第一個關注點: 我們的關注點就是說, Human 會幫我們將哪一個實現類當成入參注入進來呢?
    //todo 答案是 URL ,dubbo自己封裝的URL,  統一資源定位符, dubbo 會解析入參位置的 url中封裝的map
    //todo map中的key 與 PersonInteface中的使用   @Adaptive("person") 註解標記的value對應, 那麼值就是將要注入的實際型別
    //todo 第二個關注點: dubbo底層很可能是通過反射使用構造方法完成的屬性注入
    public void setPersonInterface(PersonInterface personInterface) {
        this.personInterface = personInterface;
    }

    @Override
    public String getName(URL url) {
        System.out.println("i am Human ");
        return "i am Human + " + personInterface.getName(url);
    }
}

可以很直接的看到, 這個實現類其實是依賴了一個 PersionInterface的屬性,需要將這個屬性注入給他, 於是問題來了, 注入的是誰呢? 下面繼續往下拉看

進入下面的方法, 主要邏輯如下

  • 根據那麼取出ExtensionClasses的 Class 物件,
    • 這獲取出來的物件就是我們前面所說的,就是讀取SPI配置檔案時獲取出來的物件
  • 呼叫injectExtension()方法, 完成物件依賴屬性的注入
  • 實現Dubbo的AOP , 完成物件方法切面的增強

我們先看下: injectExtension(instance)的實現細節:

主要邏輯如下:

  • 通過反射獲取出物件的所有的方法
    • 如果不是setter方法就返回 (體現出, Dubbo的依賴注入是藉助setter方法實現的)
    • 如果新增的@Disable註解, 表示明確指定不會進行注入
    • 嘗試獲取出當前物件所依賴的物件, 也就是下面的objectFactory.getExtension(pt,property)
      • 其中objectFactory就是前面創建出來的SPIExtensionFactory
      • pt=PersonInterface的Class 描述物件
      • property 是從 setPersonInterface()方法中截取出來的: personInterface 字串

上圖中的主要目的就是完成依賴注入, 什麼依賴注入呢? 就是在 Human.java中 依賴了一個PersonInterface型別的屬性, Dubbo需要幫我填充上 , HumanInterface.java 中鎖依賴的那個具體的實現類是誰呢? 就是在上面函式中的通過 objectFactory.getExtension(Class,name) 動態生成出來的

當我們繼續跟進這個getExtension(), 就會發現下面的現象, 看我在下圖中標出來的綠色部分, 可以發現 , 他獲取出來的 ExtensionLoader全稱如下: 它就是Dubbo我們生成出來的代理 ExtensionLoader

再進一步, 通過loader 獲取出自適應的拓展類: getAdapativeExtension()通過反編譯看一下生成的Interface是誰, 可以看一下,它的實現, 這就是為什麼Dubbo通過URL就能知道該注入誰, 用誰取幹活

Dubbo的AOP細節 (wrapper)

先說啥是AOP, 就是面向切面程式設計, 其實說白了就是對現有的物件進行增強

Dubbo是怎麼做的呢? 按照Dubbo的約定, 我們的這樣編碼:即 通過繼承+構造方法 實現AOP , 就像下面這樣

Dubbo的底層實現: 處理AOP的邏輯在下面

Dubbo會從SPI配置檔案中找到我們新增就進去的Wrapperlei, 通過構造方法反射出他們的例項,, 重要的是將反射出來的這個例項替換成了原來未被增強的 物件, 就跟java的感覺就像是升級版的靜態代理一樣

最後打一個小廣告: 我是bloger 賜我白日夢, 本科大三在讀, 熱衷java研發, 期望有一份Java相關實習崗位的工作, 可以全職實習半年左右, 最理想城市是北京, 求大佬的內推哇