探究Dubbo的拓展機制: 上


這篇博文是我決心深度學習Dubbo框架時記錄的筆記, 主題是Dubbo的拓展點, 下面的幾個部分相對來說比較零散, 貌似是不和主題掛鈎的 , 並且是一些很冷門的知識點 , 但是它們確實是深入學習Dubbo的前置知識

知識儲備一: Dubbo的架構圖

architecture

細化一下上圖的各個組成部分:

  • 服務提供者
    • 提供服務接口的實現類
    • 注冊服務 (遠程注冊, 本地注冊)
    • 對外暴露服務
  • 注冊中心
    • 保存 服務名稱&服務地址 的映射關系
    • 當服務地址發生變動時, 主動通知消費者
  • 服務消費者
    • 啟動時從注冊中心拉取服務提供者的地址, 緩存在本地
    • 根據負載均衡策略選出一個服務進行遠程調用 (Dubbo會將下面的信息封裝成對象通過網絡發送給服務提供者)
      • 參數1: 接口名
      • 參數2: 方法名
      • 參數3: 參數列表類型
      • 參數4: 參數值列表
  • 監控中心
    • 統計RPC過程的細節數據, 如: 服務調用次數, 調用時間

知識儲備二: Dubbo中的核心概念

URL

Dubbo自己有個封裝類叫URL如下: URL: 全稱 Uniform Resources Loactor 統一資源定位符, 它是不可變的, 也是線程安全的

url

  • URL的作用

其實, Dubbo它作為一款RPC通信框架, 主體功能就是負責在服務集群中各個點之間進行數據的傳遞, 打個例子比如: 服務消費者調用服務的提供者,這個過程中的通信是Dubbo框架實現的, 通信的格式就好比自定義協議一樣, Dubbo將服務提供者和服務消費者兩種之間進行數據傳遞 需要的協議信息/ 端口號信息/ 請求那個接口 / 參數信息 / 賬號 / 密碼信息. 等一系列的信息進行封裝,於是上圖中的 URL 誕生了

  • 對URL的理解

對URL最直觀的理解: URL是dobbo 對一系列數據的封裝, 方便代碼的編寫, 參數的傳遞
很多人也將URL稱為Dubbo的消息總線, 說URL貫穿於Dubbo的上下文, 我感覺到這個結論也許是這樣得出的, 就是說 Dubbo作為一款RPC框架, 首要的任務就是 RPC 遠程過程調用, 怎么樣找到提供服務的機器呢? 無論是發起socket 還是借助Thrift或者Netty這種框架實現也罷, 前提是得知道提供服務的機器在哪里, 它的哪些接口對外暴露服務 , 沒錯! 這些信息都被Dubbo封裝在了URL中

  • URL常見的組成

    • protobuf - 協議信息, 如 zk / Dubbo / http / Thrift
    • host/port - 目標主機端口信息
    • path - 接口的名稱
    • parameters - 參數鍵值對信息
  • 典型的Dubbo URL格式

# 描述 Dubbo 協議的服務
Dubbo://192.168.1.6:20880/moe.cnkirito.sample.HelloService?timeout=3000

# 描述 zookeeper 注冊中心
zookeeper://127.0.0.1:2181/org.apache.Dubbo.registry.RegistryService?application=demo-consumer&Dubbo=2.0.2&interface=org.apache.Dubbo.registry.RegistryService&pid=1214&qos.port=33333&timestamp=1545721981946

# 描述消費者 服務
consumer://30.5.120.217/org.apache.Dubbo.demo.DemoService?application=demo-consumer&category=consumers&check=false&Dubbo=2.0.2&interface=org.apache.Dubbo.demo.DemoService&methods=sayHello&pid=1209&qos.port=33333&side=consumer&timestamp=1545721827784

# for this case, url protocol = null, url host = 192.168.1.3, port = 20880, url path = null
192.168.1.3:20880

# for this case, url protocol = file, url host = null, url path = home/user1/router.js
file:///home/user1/router.js?type=script

... 更多參照URL源碼

Invoker

invoker 直譯調用者

  • 在服務提供方: invoker 對象被構造出來去調用提供服務的函數
  • 在服務的消費方: invoker用於調用 執行遠程過程調用的類

Invocation

指代程序中的調用對象, 包含了 接口名 / 方法名 / 參數類型列表 / 參數值列表 等

知識儲備三: Java SPI (Service Provider Interface )

怎么理解SPI機制呢?

如果說SPI是java提供的一種拓展機制, 其實是不明確的, 結合java本身的語言特性來說, SPI直觀的看就是 基於接口的編程 + 策略模式 + 配置文件 組合實現的動態加載機制, 用大白話解釋就是說, 一個框架的設計為了后期的拓展性, 肯定先會在頂層設計接口, 然后再為這些接口提供一些默認的實現類, 未了良好的拓展性, 如果想讓, 如果想實現允許當前框架 識別 / 加載 / 使用 第三方提供的jar包時 , 就可以使用SPI實現接口的動態加載, 只要遵循SPI的規范, java就能將我們自己的類也加載進JVM供我們使用

說起來總歸是模糊的, 看下面的小Demo自然就懂了

// 接口
public interface Person {
    String getName();
}
// 實現類一: 
public class Student implements Person {
    @Override
    public String getName() {
        return "Student";
    }
}
//  實現類二: 
public class Teacher implements Person {
    @Override
    public String getName() {
        return "Teacher";
    }
}

resources/META-INF/services/ 目錄下面添加配置文件, 文件名稱為 Person接口的全限定名, 內容如下

com.changwu.javaspi.api.Student
com.changwu.javaspi.api.Teacher

測試程序:

public class Test {
    public static void main(String[] args) {
        // 加載接口中的實現類
        ServiceLoader<Person> load = ServiceLoader.load(Person.class);
        Iterator<Person> iterator = load.iterator();
        while (iterator.hasNext()){
            Person next = iterator.next();
            System.out.println(next.getName());
        }
    }
}

測試結果控制台輸出如下:

Student
Teacher

Dubbo SPI

Dubbo自己也封裝了一套SPI機制, 並將此作為它的擴展點,如果我們有更好的想法, 可以使用Dubbo這個特性加將我們自己的類注入給Dubbo, 它用法和JDK原生的SPI相似, 不同點在哪里呢? Dubbo的更強大, 比如相對於JDK的SPI , 它支持根據名稱獲取出指定的拓展類

一個小demo

  • 接口如下 , 注意點 Dubbo的SPI需要在接口上標注注解 @SPI
@SPI
public interface PersonInterface {
   String getName();
}
  • 兩個實現類
public class Student implements PersonInterface {
    @Override
    public String getName() {
        return "Student";
    }
}

public class Teacher implements PersonInterface {
    @Override
    public String getName() {
        return "Teacher";
    }
}
  • 配置文件

配置文件

  • 測試類 可以根據名稱明確指出使用哪一個實現類
public class Test {
    public static void main(String[] args) {
        // todo 第一點:  Dubbo 的SPI算作是他的一個可擴展的機制
        ExtensionLoader<PersonInterface> extensionLoader = ExtensionLoader.getExtensionLoader(PersonInterface.class);
        PersonInterface personInterface = extensionLoader.getExtension("student");
        System.out.println(personInterface.getName());
    }
}

Dubbo IOC

Spring 的IOC肯定是鼎鼎大名的, 很直接的能想到Spring的 @Autowired 注解, 或者的配置文件版本的 <bean>標簽中可以幫我們自動維護bean之間的相互的依賴的關系

Dubbo 也實現了自己的IOC

比如下面的代碼這樣: Human.java 中依賴了 PersonInterface 類型的對象, 打眼看上去, 這個對象肯定是借助我們提供的setter方法完成的注入

public class Human implements PersonInterface {

    private PersonInterface personInterface;

    public void setpersonInterface(PersonInterface personInterface) {
        this.personInterface = personInterface;
    }

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

那么問題來了, 假如我們在配置文件中添加了多個PersonInterface接口的實現類, 那Dubbo是如何得知需要注入哪一個的呢? 答案就在入參位置的URL中, 也就是我在 知識儲備二中提到的概念URL

可以看下面這段測試代碼, 怎么讀下面的這段代碼呢?

單獨看 (PersonInterface) extensionLoader.getExtension("human"); 其實就是前面所說的 Dubbo的SPI機制, 但是在這個基礎上多出來的邏輯是啥呢? 是我們構建了一個URL, 那為什么加進去一個URL? 因為上面的示例代碼說了, human依賴了一個 PersonInterface 類型的變量, Dubbo就是根據這個URL變量, 進而得知自己到底該該注入哪一個變量Personinterface實例的 (因為我提供了兩個 一個是Student , 另一個是Teacher)

此外, 他需要的是map , 我們給它的也是一個hashmap , 特性就是HashMap的key是不重復的, 用大白話說, 它的底層肯定是 key=value 唯一綁定, 並且key也不會出現重復的情況

public class Test {
    public static void main(String[] args) {
        // todo 源碼的入口, 進入 getExtensionLoader()
        ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(PersonInterface.class);
        HashMap<String, String> map = new HashMap<>();
        map.put("human", "student");
        URL url = new URL("", "",1,map);
        // todo 繼續跟進這個方法
        PersonInterface personInterface = (PersonInterface) extensionLoader.getExtension("human");
        System.out.println(personInterface.getName(url));
    }
}

**那說了這么多, 到底注入的是哪一個對象呢? 從map.put("human", "student"); 也能很清楚的看出來, 不就是Student嗎? 是的, 確實是它, 但是還少了點東西, 就是Personinterface怎么編寫呢? 如下: **

// @SPI("stu") 可以給注解添加參數, 參數表示 PersonInterface 的默認實現類
@SPI
public interface PersonInterface {
    // todo 下面的注解很重要, 啥意思呢?  可以點進這個注解, 我有一些翻譯
    // 驗證AOP, 依然注入的信息從 url中獲取出來
    @Adaptive("human")
    String getName(URL url);
}

看上面的代碼, 除了@SPI注解, 還有一個注解就是@Adaptive注解, 這個注解的value部分決定了Dubbo到底需要注入哪一個 ExtensionObject

因為Dubbo在啟動的過程中會去讀取/META-INF/services/ Dubbo-SPI配置文件, 並將每行數據讀取維護在一個map中, key就是我們自定義的名字, 值就是左邊的全類名

看下面我們傳遞進去的是 human , 表示告訴Dubbo, 讓Dubbo拿着human去查找, 很顯然Dubbo把我們前面傳遞給它的student 找出來, 有了Student 進一步再從上下文中所有的 ExtensionObject中(包含了我們在配置文件中添加進去的Personinterface的兩個實現) 找到具體的注入對象

Dubbo AOP

還是說, AOP是面向切面編程的思想, Spring自己實現了一套, Dubbo 也實現了一套

驗證Dubbo的AOP實現類如下:

  • Dubbo的AOP增強實現和靜態代理的編碼方式相似, 比如我們就增強 PersonInterface中的方法, 所以我們繼承PersonInterface, 提供構造方法入, 留給Dubbo通過反射完成指定目標對象的注入, 並在注入進來的目標對象的目標方法前后植入增強的邏輯
public class PersonWrapper implements PersonInterface {

    // todo 驗證Dubbo的自動注入
    private PersonInterface personInterface;
    // todo 根據構造方法進行注入
    public PersonWrapper(PersonInterface in){ // 假設傳遞進來的就是具體的實現類
        this.personInterface=in;
    }

    // todo 當我們將 personWrapper 配置進 Dubbo的 spi中時, 通過Dubbo的Spi獲取personInterface執行時,下面的方法就會被執行
    @Override
    public String getName() {
        System.out.println("before... ");
        String color = personInterface.getName();
        System.out.println("after... ");
        return "getName";
    }
}
  • 那如何讓Dubbo知道包含我們增強邏輯的 PersonWrapper對象呢? 還是老樣子, 通過配置文件完成, 如下:

配置文件2

  • 測試如下:
 public static void main(String[] args) {
        // todo 第一點:  Dubbo 的SPI算作是他的一個可擴展的機制
        ExtensionLoader<PersonInterface> extensionLoader = ExtensionLoader.getExtensionLoader(PersonInterface.class);
        PersonInterface personInterface = extensionLoader.getExtension("student");
        System.out.println(personInterface.getName());
    }

結果如下:

before...
after...
getName  

**如過存在多個AOP增強類, 比如從上到下出現的順序是 w1 w2 ... 那么增強的邏輯添加順序是 before2 before1 **

結語:

​下一篇博文就是探究Dubbo的這些拓展點的底層實現細節了 , 還是挺帶勁的...

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM