上一篇講到了 Dubbo SPI 使用方法(1) - 擴展點自動包裝。
本文接着講 Dubbo SPI - 擴展點自適應。java
大綱apache
@Adaptive 註解使用方法segmentfault
ExtensionLoader 注入的依賴擴展點是一個 Adaptive 實例,直到擴展點方法執行時才決定調用是哪個擴展點實現。Dubbo 使用 URL 對象(包含了Key-Value)傳遞配置信息。數組
擴展點方法調用會有 URL 參數(或是參數有URL成員)這樣依賴的擴展點也能夠從URL拿到配置信息,全部的擴展點本身定好配置的 Key 後,配置信息從 URL 上從最外層傳入。URL在配置傳遞上便是一條總線。瀏覽器
上面摘自官網的一段介紹。url
劃重點:.net
也能夠是包含 URL 成員的參數代理
跟 擴展點自動包裝的區別調試
經過 URL 中的參數,決定調用哪一個擴展類實現日誌
若是仍是很差理解,就繼續看下面的案例。
要想實現 擴展點自適應,須要藉助 @Adaptive
註解,該註解能夠做用在兩個地方:
在類上,表示該類是一個擴展類,不須要生成代理直接用便可;
在方法上則表示該方法需生成代理, 此時就須要用到上面提到的 URL 參數
這個相對比較簡單,沒什麼特別的地方,上面也有提到,當 @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()
當 @Adaptive 註解做用在擴展接口方法上時,方法中須要傳入一個 URL 參數,或者包裝有 URL 的參數時,會經過動態編譯得到一個 Adaptive 實例
使用以下:
interface Protocol { // 關鍵字 2 : Key // 這裏定義一個 Key,由於是數組,因此能夠傳多個 // Key 的做用會在後面看到 @Adaptive({"key1"}) void export(URL url) }
篇幅緣由,只貼出一個 DubboProtocol
class DubboProtocol implements Prototol { void export(URL url) { print("我是 dubbo protol") } }
dubbo=com.xx.Dubboprotocol
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);
程序運行時,會通過動態編譯過程生成 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); } }