Dubbo SPI 使用方法(二)- 擴展點自適應

開篇

上一篇講到了 Dubbo SPI 使用方法(1) - 擴展點自動包裝
本文接着講 Dubbo SPI - 擴展點自適應。java

正文

大綱apache

  • 擴展點自適應介紹
  • @Adaptive 註解使用方法segmentfault

    • 做用在類上
    • 做用在方法上

1. 擴展點自適應

ExtensionLoader 注入的依賴擴展點是一個 Adaptive 實例,直到擴展點方法執行時才決定調用是哪個擴展點實現。

Dubbo 使用 URL 對象(包含了Key-Value)傳遞配置信息。數組

擴展點方法調用會有 URL 參數(或是參數有URL成員)這樣依賴的擴展點也能夠從URL拿到配置信息,全部的擴展點本身定好配置的 Key 後,配置信息從 URL 上從最外層傳入。URL在配置傳遞上便是一條總線。瀏覽器

上面摘自官網的一段介紹。url

劃重點:.net

  • 擴展方法中有 URL 參數

    也能夠是包含 URL 成員的參數代理

  • 直到擴展點方法執行時,才決定調用哪一個擴展點實現

    擴展點自動包裝的區別調試

  • 經過 URL 傳遞配置信息

    經過 URL 中的參數,決定調用哪一個擴展類實現日誌

若是仍是很差理解,就繼續看下面的案例。

2. @Adaptive 註解

要想實現 擴展點自適應,須要藉助 @Adaptive註解,該註解能夠做用在兩個地方:

  • 擴展實現類上

在類上,表示該類是一個擴展類,不須要生成代理直接用便可;

  • 擴展接口方法上

在方法上則表示該方法需生成代理, 此時就須要用到上面提到的 URL 參數

2.1 做用在擴展實現類上

這個相對比較簡單,沒什麼特別的地方,上面也有提到,當 @Adaptive做用在類上,就表示該類是一個擴展類。

再說的簡單點就是:

若是做用在方法會幫咱們在運行時動態生成一個 Adaptive 實例,

若是做用在類上就至關於本身定義了一個現成的。

// 定義一個擴展接口
interface HelloService {
    void sayHello();
}

// 定義一個自適應擴展類
@Adaptive
class HelloServiceAdaptive implements HelloSerivce{
    void sayHello(){
        // doSomthing
    }
}

ExtensionLoader<HelloService> extensionLoader =
        ExtensionLoader.getExtensionLoader(HelloService.class);
        
// 獲取 Adaptive 實例        
HelloService helloservice = extensionLoader.getAdaptiveExtension()
2.2 做用在擴展接口方法上

當 @Adaptive 註解做用在擴展接口方法上時,方法中須要傳入一個 URL 參數,或者包裝有 URL 的參數時,會經過動態編譯得到一個 Adaptive 實例

使用以下:
  1. 定義一個擴展接口:
interface Protocol {
    // 關鍵字 2 : Key
    // 這裏定義一個 Key,由於是數組,因此能夠傳多個
    // Key 的做用會在後面看到
    @Adaptive({"key1"})
    void export(URL url)
}
  1. 定義多個擴展接口的實現類
篇幅緣由,只貼出一個 DubboProtocol
class DubboProtocol implements Prototol {
    
    void export(URL url) {
        print("我是 dubbo protol")
    }
    
}
  1. 配置 META-INF/dubbo/com.xx.Prototol 文件
dubbo=com.xx.Dubboprotocol
  1. 程序入口
Protol protol = extensionLoader.getAdaptiveExtension()


// 把步驟一 中的 Key 做爲 「鍵」 傳入 map 中,
// value 對應步驟三定義的:擴展接口的實現的名稱
// 若是定義多個 key,這個也穿多個
HashMap<String, String> params = new HashMap<>();
params.put("key2", "dubbo");

// 定義一個 URL,
URL url = new URL("dubbo", "localhost", 8080, params);

protocol.export(url);
  1. 動態生成Adaptive 實例

程序運行時,會通過動態編譯過程生成 Protocal 對應的 Adaptive 實例, 即 Protocol$Adaptive

具體來說:就是在程序運行過程當中,根據條件,經過拼接字符串的形式生成 java 源碼,而後進行編譯得到對應的實例

調試 Dubbo 源碼時,修改日誌級別爲 DEBUG ,控制檯會打印出源碼

(文末貼出了 Dubbo 動態編譯出來的 Protocol$Adaptive):

下面是當 @Adaptive 註解做用在 Protocol 擴展接口上 (自定義的一個接口,不是 Dubbo 中那個),運行時產生的 Adaptive 實例對應的源碼。

class Protocol$Adaptive implements Protocol {

    // 這裏全是僞代碼
    void export(URL url) {
        // 獲取 url 的參數, 好比:dubbo
        // 若是 key1 不存在,會從其餘 Key(key2,keyn..)中獲取
        String extName = url.get("key1")
        // 獲取具體擴展實現類
        DubboProtocol protocol = getExtensition(extName);
        // 調用 export 方法
        protocol.export(url)
    }
    
}

總結

擴展點自適應就是利用 @Adaptive 註解,來獲取對應擴展接口的 Adaptive 實例。

若是註解做用在類上,那麼這個類就會被直接標記成一個 Adaptive;

若是註解做用在方法上,會經過動態編譯技術,動態生成一個只包含該方法的 Adaptive;

二者有什麼區別呢?

舉個不恰當的例子;

有一個需求是瀏覽器發起一個請求到後臺,後臺會跳轉到另外一個 URL;

前者更像是我已經明確知道要跳轉的 URL 是什麼了,我直接定死在後臺代碼;

後者則是我不知道要跳轉到哪,跳轉 URL 須要瀏覽器傳過來,我再根據這個參數去跳轉。

下篇文章會經過 Dubbo 的 Protocol 擴展點來舉例說明。

附錄

Dubbo 動態編譯生成的 Protocol$Adaptive

package com.nimo.dubbospi.protocol;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

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

    public java.util.List getServers() {
        throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}
相關文章
相關標籤/搜索