[轉] 理解 Dubbo SPI 擴展機制

寫在前面

    最近接觸了 gRPC 體會到雖然衆多 RPC 框架各有各的特色可是他們提供的特性和功能有不少的類似之處 , 這就說明他們面對一樣的分佈式系統帶來的問題。從 2016 年左右開始接觸到 dubbo ,基本停留在使用的層面,對 dubbo 的設計以及着重要解決的問題都沒有系統的研究過,經過對 dubbo 和其餘相似 RPC 產品的系統學習 ,學習分佈式系統中面臨的共同問題以及解決之道。 java

微內核架構

    微內核架構 (Microkernel architecture) 模式也被稱爲插件架構 (Plugin architecture) 模式。本來與內核集成在一塊兒的組件會被分離出來,內核提供了特定的接口使得這些組件能夠靈活的接入,這些組件在內核的管理下工做,可是這些組件能夠獨立的發展、更改(不會對現有系統形成改動),只要符合內核的接口便可。典型的例子好比 , Eclipse , IDEA 。spring

                       

Dubbo 的微內核設計

    根據我我的對 Dubbo 微內核設計的理解,以及閱讀源碼後總結。視覺老是最直觀的,可讓大腦最快速度的有一個最直觀的認識,一開始就一頭深刻到源碼的細節中只會讓人迷糊。不理解 Dubbo 的微內核設計架構的話,學習起來會走很多彎路。架構

                

    dubbo 內核對擴展是無感的 , 徹底不知道擴展的存在 , 內核代碼中不會出現使用具體擴展的硬編碼。app

    術語說明 :框架

 SPI : Service Provider Interface 。分佈式

 擴展點 : 稱 Dubbo 中被 @SPI 註解的 Interface 爲一個擴展點。ide

 擴展 : 被 @SPI 註解的 Interface 的實現稱爲這個擴展點的一個擴展。函數

Dubbo SPI  約定

    擴展點約定 :  擴展點必須是 Interface 類型 , 必須被 @SPI 註解 , 知足這兩點纔是一個擴展點。學習

    擴展定義約定 : 在 META-INF/services/$擴展點接口的全類名 , META-INF/dubbo/$擴展點接口的全類名 , META-INF/dubbo/internal/$擴展點接口的全類名 , 這些路徑下定義的文件名稱爲 $擴展點接口的全類名 , 文件中以鍵值對的方式配置擴展點的擴展實現。例如文件 META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory 中定義的擴展 :編碼

adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory

    默認適應擴展 : 被 @SPI("abc") 註解的 Interface  , 那麼這個擴展點的缺省適應擴展就是 SPI 配置文件中 key 爲 "abc"  的擴展。若是存在被 @Adaptive 註解在類上的擴展點接口實現 ,那麼這個類就做爲擴展點的缺省適應擴展, 一個擴展點只能有一個缺省適應擴展也就是說多個擴展中只能有一個在類上被 @Adaptive 註解,若是有多個 dubbo 會拋出 IllegalStateException("More than 1 adaptive class found : ")。

@SPI 、@Adaptive 、@Activate 做用

 @SPI (註解在類上): @SPI 註解標識了接口是一個擴展點 , 屬性 value 用來指定默認適配擴展點的名稱。

@Activate (註解在類型和方法上) : @Activate 註解在擴展點的實現類上 ,表示了一個擴展類被獲取到的的條件,符合條件就被獲取,不符合條件就不獲取 ,根據 @Activate 中的 group 、 value 屬性來過濾 。具體參考 ExtensionLoader 中的  getActivateExtension 函數。

@Adaptive (註解在類型和方法上) :  @Adaptive 註解在類上 , 這個類就是缺省的適配擴展。@Adaptive 註解在擴展點 Interface 的方法上時 , dubbo 動態的生成一個這個擴展點的適配擴展類(生成代碼 ,動態編譯實例化 Class ),名稱爲 擴展點 Interface 的簡單類名 + $Adaptive ,例如 : ProxyFactory$Adpative  。這麼作的目的是爲了在運行時去適配不一樣的擴展實例 , 在運行時經過傳入的 URL 類型的參數或者內部含有獲取 URL 方法的參數 ,從 URL 中獲取到要使用的擴展類的名稱 ,再去根據名稱加載對應的擴展實例 ,用這個擴展實例對象調用相同的方法  。若是運行時沒有適配到運行的擴展實例 , 那麼就使用 @SPI 註解缺省指定的擴展。經過這種方式就實現了運行時去適配到對應的擴展。

        運行時動態生成的適配擴展類代碼 :

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;

        // 從 URL 中獲取擴展名稱 , "dubbo" 是從 ExtensionLoader 對象中的 cachedDefaultName 
        // 屬性獲取到的 , cachedDefaultName 是擴展點上 @SPI 註解中 value 屬性指定的 
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        
        // 經過擴展名稱獲取擴展實例對象 , 調用擴展實例對象的相同方法 
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        
        // 從 URL 中獲取擴展名稱 
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");

        // 經過擴展名稱獲取擴展實例對象 , 調用擴展實例對象的相同方法
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}
package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory {
    public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();

        // 從 URL 中獲取擴展名稱 
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");

        // 經過擴展名稱獲取擴展實例對象 , 調用擴展實例對象的相同方法
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0);
    }

    public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws java.lang.Object {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg2;

        // 從 URL 中獲取擴展名稱 
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");

        // 經過擴展名稱獲取擴展實例對象 , 調用擴展實例對象的相同方法
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getInvoker(arg0, arg1, arg2);
    }
}

        這些代碼都是模板代碼 , 最核心的代碼就只有一行 , 這行代碼是去獲取一個指定名稱的擴展實例對象 :

ExtensionLoader.getExtensionLoader(Xxx.class).getExtension(extName);

        在使用 dubbo 生成的源碼時要注意 , 它生成的代碼是有錯誤的, 好比 Protocol$Adpative 類的中的 refer 和 export 方法簽名 throws 的不是 Protocol 接口中方法定義拋出的 RpcException , 而是 Class ,和 Invoker  。 這個問題存在於 dubbo 2.5.4 以前的版本中,在 2.5.4 版本中修復了。

擴展加載器 ExtensionLoader 

        擴展加載器絕對是一個核心組件了 ,它控制着 dubbo 內部全部擴展點的初始化、加載擴展的過程。這個類的源碼是頗有必要深刻學習的。從 Dubbo 內核設計簡圖能夠看到,如今的學習尚未接觸到 dubbo 的內核。

            

        ExtensionLoader 中會存儲兩個靜態屬性 , EXTENSION_LOADERS 保存了內核開放的擴展點對應的 ExtensionLoader 實例對象 (說明了一種擴展點有一個對應的 ExtensionLoader 對象)。EXTENSION_INSTANCES 保存了擴展類型 (Class) 和擴展類型的實例對象。

        ExtensionLoader  對象中的屬性 :

Class<?> type;

ExtensionFactory objectFactory;

ConcurrentMap<Class<?>, String> cachedNames;

Holder<Map<String, Class<?>>> cachedClasses;

Map<String, Activate> cachedActivates;

Class<?> cachedAdaptiveClass;

ConcurrentMap<String, Holder<Object>> cachedInstances;

String cachedDefaultName;

Holder<Object> cachedAdaptiveInstance;

Throwable createAdaptiveInstanceError;

Set<Class<?>> cachedWrapperClasses;

Map<String, IllegalStateException> exceptions;

type : 被 @SPI 註解的 Interface , 也就是擴展點。

objectFactory : 擴展工廠,能夠從中獲取到擴展類型實例對象 ,缺省爲 AdaptiveExtensionFactory。 

cachedNames : 保存不知足裝飾模式(不存在只有一個參數,而且參數是擴展點類型實例對象的構造函數)的擴展的名稱。

        cachedClasses : 保存不知足裝飾模式的擴展的 Class 實例 , 擴展的名稱做爲 key , Class 實例做爲 value。

        cachedActivates : 保存不知足裝飾模式 , 被 @Activate 註解的擴展的 Class 實例。

        cachedAdaptiveClass : 被 @Adpative 註解的擴展的 Class 實例 。

        cachedInstances : 保存擴展的名稱和實例對象 , 擴展名稱爲 key  , 擴展實例爲 value。

        cachedDefaultName :  擴展點上 @SPI 註解指定的缺省適配擴展。

        createAdaptiveInstanceError : 建立適配擴展實例過程當中拋出的異常。

        cachedWrapperClasses : 知足裝飾模式的擴展的 Class 實例。

        exceptions : 保存在加載擴展點配置文件時,加載擴展點過程當中拋出的異常 , key 是當前讀取的擴展點配置文件的一行 , value 是拋出的異常。

附: dubbo 開放的擴展點 

com.alibaba.dubbo.cache.CacheFactory
com.alibaba.dubbo.common.compiler.Compiler
com.alibaba.dubbo.common.extension.ExtensionFactory
com.alibaba.dubbo.common.logger.LoggerAdapter
com.alibaba.dubbo.common.serialize.Serialization
com.alibaba.dubbo.common.status.StatusChecker
com.alibaba.dubbo.common.store.DataStore
com.alibaba.dubbo.common.threadpool.ThreadPool
com.alibaba.dubbo.container.Container
com.alibaba.dubbo.container.page.PageHandler
com.alibaba.dubbo.monitor.MonitorFactory
com.alibaba.dubbo.registry.RegistryFactory
com.alibaba.dubbo.remoting.Codec2
com.alibaba.dubbo.remoting.Dispatcher
com.alibaba.dubbo.remoting.exchange.Exchanger
com.alibaba.dubbo.remoting.http.HttpBinder
com.alibaba.dubbo.remoting.p2p.Networker
com.alibaba.dubbo.remoting.telnet.TelnetHandler
com.alibaba.dubbo.remoting.Transporter
com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter
com.alibaba.dubbo.rpc.cluster.Cluster
com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory
com.alibaba.dubbo.rpc.cluster.LoadBalance
com.alibaba.dubbo.rpc.cluster.Merger
com.alibaba.dubbo.rpc.cluster.RouterFactory
com.alibaba.dubbo.rpc.Filter
com.alibaba.dubbo.rpc.InvokerListener
com.alibaba.dubbo.rpc.Protocol
com.alibaba.dubbo.rpc.protocol.thrift.ClassNameGenerator
com.alibaba.dubbo.rpc.ProxyFactory
com.alibaba.dubbo.validation.Validation

 

原文連接:https://my.oschina.net/j4love/blog/1813040

相關文章
相關標籤/搜索