?? Dubbo SPI 之 Adaptive 自適應類

翻看 Dubbo 的源碼奠伪,不難發(fā)現(xiàn)烧颖,框架到處都在用 SPI 機制進行擴展。這是由于 Dubbo 框架對各種層做了很多的實現(xiàn)方式艺普,然后由用戶自己去選擇具體的實現(xiàn)方式。比如 Protocol鉴竭,Dubbo 提供的實現(xiàn)就有 dubbo歧譬,hessianthrift搏存,redis瑰步,inJvm 等等。這么多的實現(xiàn)方式璧眠,Dubbo 是如何做到動態(tài)的切換的呢缩焦?

接口自適應與 @Adaptive 注解

Dubbo 沒有采用 JDK 提供的 SPI 機制,而是自己實現(xiàn)了一套责静,增強了很多功能袁滥。具體細節(jié)可以瀏覽網(wǎng)上其他博客。

Dubbo 充分利用面向對象思想泰演,每一個組件內(nèi)引入其他組件都是以接口的形式進行依賴呻拌,動態(tài)的 inject 實現(xiàn)類。所以這種思想上也用到了 AOPDI 的思想睦焕。
我們定義一個接口 Registry藐握,假定它的功能是將本地服務暴露到注冊中心,以及從注冊中心獲取可用服務垃喊,屬于 RPC 框架中的服務注冊與發(fā)現(xiàn)組件猾普。

@SPI("zookeeper")
public interface Registry {
    /**
     * 注冊服務
     */
    @Adaptive()
    String register(URL url, String content);
    /**
     * 發(fā)現(xiàn)服務
     */
    @Adaptive()
    String discovery(URL url, String content);
}
  • @SPI 注解標注這是一個可擴展的組件,注解內(nèi)的內(nèi)容后文詳細介紹本谜。
  • 該接口定義了兩個方法 registerdiscovery初家,分別代表注冊服務和發(fā)現(xiàn)服務。兩個方法中都有 URL 參數(shù)乌助,該類是 Dubbo 內(nèi)置的類溜在,代表了 Dubbo 整個執(zhí)行過程中的上下文信息,包括各類配置信息他托,參數(shù)等掖肋。content 代表備注信息。
  • @Adaptive()注解表明這兩個方法都是自適應方法赏参,具體作用后文分析志笼。

下面分別是兩個實現(xiàn)類 ZookeeperRegistryEtcdRegistry

ZookeeperRegistry

public class ZookeeperRegistry implements Registry {
    private Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class);

    @Override
    public String register(URL url, String content) {
        logger.info("服務: {} 已注冊到zookeeper上把篓,備注: {}", url.getParameter("service"), content);

        return "Zookeeper register already! ";
    }

    @Override
    public String discovery(URL url, String content) {
        logger.info("zookeeper上發(fā)現(xiàn)服務: {} , 備注: {}", url.getParameter("service"), content);

        return "Zookeeper discovery already! ";
    }
}

EtcdRegistry

public class EtcdRegistry implements Registry {

    private Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class);

    @Override
    public String register(URL url, String content) {
        logger.info("服務: {} 已注冊到 Etcd 上纫溃,備注: {}", url.getParameter("service"), content);

        return "Etcd register already! ";
    }

    @Override
    public String discovery(URL url, String content) {
        logger.info("Etcd 上發(fā)現(xiàn)服務: {} , 備注: {}", url.getParameter("service"), content);

        return "Etcd discovery already! ";
    }
}

我們可以將服務注冊信息注冊到老牌注冊中心 zookeeper 上,或者使用新興流行輕量級注冊中心 etcd 上韧掩。

配置擴展點實現(xiàn)信息到 Resource 目錄下

Resource 下的 META-INF.dubbo 下新建 以Registry 全限定名為名的文件紊浩,配置實現(xiàn)類信息,以 key-value 的形式疗锐。(這里要注意與 JDK 默認的 SPI 機制的區(qū)別)

spi.png

文件內(nèi)內(nèi)容如下:

etcd=com.maple.spi.impl.EtcdRegistry
zookeeper=com.maple.spi.impl.ZookeeperRegistry

測試 Dubbo SPI 自適應類

public class Main {
    public static void main(String[] args) {
        URL url = URL.valueOf("test://localhost/test")
                      .addParameter("service", "helloService");

        Registry registry = ExtensionLoader.getExtensionLoader(Registry.class)
                                           .getAdaptiveExtension();
        String register = registry.register(url, "maple");

        System.out.println(register);
    }
}

該程序首先通過 Registry 接口得到它專屬的 ExtensionLoader 實例郎楼,然后調(diào)用 getAdaptiveExtension 拿到該接口的自適應類。Dubbo 會判斷是否有實現(xiàn)類(即實現(xiàn)了 Registry 接口) 上有注解 @Adaptive窒悔,如果沒有就會動態(tài)生成呜袁。本例子將會動態(tài)生成。

直接運行程序結果如下,程序最終選擇的實現(xiàn)類是 ZookeeperRegistry简珠,控制臺結果如下:

09-20 23:43:13 323 main INFO - 服務: helloService 已注冊到zookeeper上阶界,備注: maple
Zookeeper register already! 

上面代碼我們沒有看到任何實現(xiàn)類的信息,Dubbo SPI 機制會為動態(tài)的去調(diào)用實現(xiàn)類聋庵。

我們重點分析 getAdaptiveExtension方法找到的是 Registry 的自適應類膘融,可以理解為是 Registry 的一個 適配器和代理類。如果該適配器類不存在祭玉,Dubbo 會通過動態(tài)代理方式在運行時自動生成一個自適應類氧映。

打開 DEBUG 日志,在控制臺我們看到了 Dubbo 生成的類的源碼如下:

package com.maple.spi;

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

public class Registry$Adaptive implements com.maple.spi.Registry {
    public java.lang.String register(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("registry", "zookeeper");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.maple.spi.Registry) name from url(" + url.toString() + ") use keys([registry])");
        com.maple.spi.Registry extension = (com.maple.spi.Registry) ExtensionLoader.getExtensionLoader(com.maple.spi.Registry.class).getExtension(extName);
        return extension.register(arg0, arg1);
    }

    public java.lang.String discovery(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("registry", "zookeeper");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.maple.spi.Registry) name from url(" + url.toString() + ") use keys([registry])");
        com.maple.spi.Registry extension = (com.maple.spi.Registry) ExtensionLoader.getExtensionLoader(com.maple.spi.Registry.class).getExtension(extName);
        return extension.discovery(arg0, arg1);
    }
}

看代碼脱货,該適配器類的作用是類似于 AOP 的功能岛都,再調(diào)用具體的實現(xiàn)類之前律姨,先通ExtensionLoader.getExtensionLoader(Registry.class).getExtension(extName);
根據(jù) extNameloader 具體的實現(xiàn)類,然后再去調(diào)用實現(xiàn)類的相應的方法臼疫。

分析上面代碼中的一句:

 String extName = url.getParameter("registry", "zookeeper");

extName 可以通過 url 進行傳遞择份,默認值為 zookeeper, 該默認值即為我們定義的接口上的注解 @SPI 里的內(nèi)容,上文我們定義的 @SPI("zookeeper")烫堤,所以這里的默認值為 zookeeper, 當 url 中沒有對應的參數(shù)時荣赶,我們會去拿默認值。

我們可以修改 Main 測試程序鸽斟,增加 keyregistryparameter

public static void main(String[] args) {
        URL url = URL.valueOf("test://localhost/test").addParameter("service", "helloService")
                .addParameter("registry","etcd");

        Registry registry = ExtensionLoader.getExtensionLoader(Registry.class).getAdaptiveExtension();

        String register = registry.register(url, "maple");

        System.out.println(register);

    }

URL 中增加 Key拔创,并設置值為 etcd,運行程序富蓄,結果如下:

09-20 23:44:00 009 main INFO - 服務: helloService 已注冊到 Etcd 上剩燥,備注: maple
Etcd register already! 

實現(xiàn)類已經(jīng)切換為 EtcdRegistry 了。

@Adaptive 注意細節(jié)

@Adaptive 源碼

public @interface Adaptive {
    String[] value() default {};
}

細心的讀者已經(jīng)發(fā)現(xiàn)了 @Adaptivevalue這個屬性格粪。上文在接口方法上定義的 @Adaptive 是沒有設置值的躏吊。如果沒有定義值,Dubbo 默認會使用一種策略生成帐萎。這種策略是將類名定義的駝峰法則轉換為小寫比伏,并以 .號區(qū)分。 例如上文的接口名為 Registry疆导,那么這個Key 值就是 registry赁项。如果接口名為 HelloWorldKey 值就為 hello.world澈段。

當然如果 @Adaptive 是有值的話悠菜,優(yōu)先按里面的這個值來作為 Key,例如 Dubbo 框架中的接口 RegistryFactory ,該接口的自適應類將會從 URLprotocolkey 來找實現(xiàn)類的 extName败富。

@SPI("dubbo")
public interface RegistryFactory {
   @Adaptive({"protocol"})
    Registry getRegistry(URL url);
}

總結

Dubbo Adaptive 模式在整個框架中運用十分廣泛悔醋,如果用戶沒有在 URL 中進行自定義,Dubbo 默認會去加載擴展點接口上 @SPI 標注的內(nèi)容,如果此注解沒有值兽叮,那么我們就必須要在 URL 中進行值的傳遞了芬骄。如果我們想覆蓋 Dubbo 默認的實現(xiàn)策略○写希可以通過在 URL 中增加 key-value 的形式來改變账阻。

這樣一個組件充分體現(xiàn)了設計模式,對修改關閉,對擴展開放的原則泽本。當我們想自己實現(xiàn) Dubbo 中的某個組件時淘太,我們完全可以通過 Dubbo Adaptive 來動態(tài)的切換程序使用我們提供的組件。

徹底理解 Dubbo SPI 模式,以及 Adaptive 自適應類, Activate 激活類等蒲牧,是看 Dubbo 源碼的基礎撇贺。如果我們想十分流暢的去分析 Dubbo 內(nèi)部其他組件的實現(xiàn)機制,第一道要跨過的坎便是 Dubbo SPI造成。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末显熏,一起剝皮案震驚了整個濱河市雄嚣,隨后出現(xiàn)的幾起案子晒屎,更是在濱河造成了極大的恐慌,老刑警劉巖缓升,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鼓鲁,死亡現(xiàn)場離奇詭異,居然都是意外死亡港谊,警方通過查閱死者的電腦和手機骇吭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歧寺,“玉大人燥狰,你說我怎么就攤上這事⌒笨穑” “怎么了龙致?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長顷链。 經(jīng)常有香客問我目代,道長,這世上最難降的妖魔是什么嗤练? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任榛了,我火速辦了婚禮,結果婚禮上煞抬,老公的妹妹穿的比我還像新娘霜大。我一直安慰自己,他們只是感情好革答,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布战坤。 她就那樣靜靜地躺著,像睡著了一般蝗碎。 火紅的嫁衣襯著肌膚如雪湖笨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天蹦骑,我揣著相機與錄音慈省,去河邊找鬼。 笑死,一個胖子當著我的面吹牛边败,可吹牛的內(nèi)容都是我干的袱衷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼笑窜,長吁一口氣:“原來是場噩夢啊……” “哼负懦!你這毒婦竟也來了?” 一聲冷哼從身側響起送淆,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤挠蛉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后断傲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脱吱,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年认罩,在試婚紗的時候發(fā)現(xiàn)自己被綠了箱蝠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡垦垂,死狀恐怖宦搬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情劫拗,我是刑警寧澤间校,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站杨幼,受9級特大地震影響撇簿,放射性物質發(fā)生泄漏。R本人自食惡果不足惜差购,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一四瘫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧欲逃,春花似錦找蜜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至彰居,卻和暖如春诚纸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陈惰。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工畦徘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓井辆,卻偏偏與公主長得像关筒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子杯缺,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容