1. 程式人生 > >7、dubbo原始碼分析 之 服務本地暴露

7、dubbo原始碼分析 之 服務本地暴露

在上一篇文章我們分析了一下 dubbo 在服務暴露發生了哪些事,今天我們就來分析一下整個服務暴露中的本地暴露。(PS:其實我感覺本地暴露蠻雞肋的)。本地暴露需要服務提供方與服務消費方在同一個 JVM。下面我們來寫一個本地暴露使用的例子:

1) DemoService.java

public interface DemoService {

    String sayHello(String name);

}

2) DemoServiceImpl.java

public class DemoServiceImpl implements DemoService {
public String sayHello(String name) { return "Hello " + name + ", response form provider: " + RpcContext.getContext().getLocalAddress(); } }

3) application.xml – Spring配置檔案

<!-- 提供方應用資訊,用於計算依賴關係 -->
<dubbo:application name="demo-provider"/>

<!-- 和本地bean一樣實現服務 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" /> <!-- 宣告需要暴露的服務介面 --> <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" registry="N/A" scope="local"/> <dubbo:reference id="demoServiceDubbo" interface="com.alibaba.dubbo.demo.DemoService"
injvm="true"/>

4) Provider.java – 呼叫本地暴露的服務

public class Provider {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

        DemoService demoServiceDubbo = context.getBean("demoServiceDubbo", DemoService.class);
        String returnMessage = demoServiceDubbo.sayHello("carl");
        System.out.println(returnMessage);
    }

}

使用context.getBean("demoServiceDubbo", DemoService.class)這種方式來獲取Bean還不如使用context.getBean("demoService", DemoService.class)來獲取真正物件。前一種方式獲取到是 dubbo 返回的代理類,其中可以獲取到 dubbo 的InvokerListenerFilter這兩個擴充套件點。可能這是與整個服務保持統一性吧。(大家如果有不同的觀點歡迎留言)。下面我們就從原始碼的角度來分析一下 dubbo 的本地暴露吧。

如果大家看過dubbo官網的API配置(建議大家分析碼源的時候都使用API的形式呼叫,儘量少的引入第三方Jar包比如xml配置dubbo),我們就可以知道。dubbo服務的暴露的起點是ServiceConfig#export。下面我們就以這個方法以起點來分析它:

這裡寫圖片描述

export這個方法就是dubbo服務暴露的入口,主要就是判斷這個服務是否暴露以及通過ScheduledThreadPoolExecutor這個執行緒池類支援延遲暴露。

這裡寫圖片描述

接下來就是doExport方法,在這個方法的前面就是做一些 check 操作,不是重點,就不一一分析了。我們主要看一下它的appendProperties方法以及doExportUrls這兩個方法。appendProperties()方法主要是為當前物件通過setter 方法來新增屬性,它主要是通過以下方式來新增屬性:

  • 從 System 中獲取屬性key值的優先通訊是: dubbo.provider. + 當前類 Id + 當前屬性名稱 > dubbo.provider. + 當前屬性名稱 為 key 獲取值.
  • 首先從特定properties檔案載入屬性:首先 System.getProperty("dubbo.properties.file")獲取到檔案路徑,如果獲取不到就會試圖載入 dubbo 的預設的路徑 dubbo.properties載入。獲取屬性的 key 和上面從 System 裡面獲取的規則一樣。

然後我們就來分析一下doExportUrl這個方法,因為 Dubbo 允許配置多協議,在不同服務上支援不同協議或者同一服務上同時支援多種協議。所以這裡需要迴圈各個協議進行多協議暴露服務。

private void doExportUrls() {
    List<URL> registryURLs = loadRegistries(true);
    for (ProtocolConfig protocolConfig : protocols) {
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

然後我們來分析一下ServiceConfig#doExportUrlsFor1Protocol,首先我們來看一下appendParameters(Map, Object)這個方法,這個方法的作用就是通過呼叫當前物件的getter方法獲取到傳入物件的值然後塞到 map 當中去。用於後面的構造URL這個物件。

public final class URL implements Serializable {

    private static final long serialVersionUID = -1985165475234910535L;
    // 協議
    private final String protocol;
    // 使用者名稱
    private final String username;
    // 密碼
    private final String password;
    // 主機
    private final String host;
    // 埠
    private final int port;
    // path
    private final String path;
    // 其它引數
    private final Map<String, String> parameters;
}

注:URL,配置資訊的統一格式,所有擴充套件點都通過傳遞 URL 攜帶配置資訊)

這裡寫圖片描述

如果大家沒有看過我6、dubbo原始碼分析 之 服務暴露概述在這點我再向大家提醒一下,dubbo的物件扭轉過程是:

服務配置類 –> Invoker –> Exporter

首先通過 ref (服務實現類)、服務介面類以及 URL 預設通過 javassist,也就是 JavassistProxyFactory類獲取到代理物件。

這裡寫圖片描述

然後再把 Invoker 物件轉化成Exporter物件。還記得之前的5.dubbo原始碼分析 之 SPI分析之前是分析的遠端暴露中的獲取Protocol 例項。只是這裡的 protocol例項是 本地方法暴露獲取的例項。它也是 dubbo 自定義的 SPI 生成的 Protocol$Adaptive通過它的getExtension(name)方法建立 Protocol例項,然後通過 Protocol#export 方法獲取Exporter

這裡寫圖片描述

我們可以看到Protocol$Adaptive這個類生成的 Protocol物件的結構是:

  • ProtocolListenerWrapper
    • ProtocolFilterWrapper
      • InjvmProtocol

dubbo 的定義 SPI 裡面包括 AOP,其實就是獲取到所有的 SPI 介面的例項物件。然後在呼叫 getExtension(name)方法返回指定名字的擴充套件的時候會判斷哪些實現類的構造器只包含 SPI 介面就會進行代理。這裡的name是從 URL 中獲取協議。在呼叫ServiceConfig#loadRegistries方法的裡面返回的 URL 格式為registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?xxx=xxx然後再呼叫ServiceConfig#exportLocal(URL)方法的時候裡面把協議(protocol)設定成injvm, 然後 在 Protocol$Adaptive 進行服務暴露的時候:

這裡寫圖片描述

因為 duubo 預設在dubbo-rpc-injvm的自定義Protocol配置檔案(${dubbo-rpc-injvm}/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol)配置的是injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol所以得到的物件是InjvmProtocol。因為ProtocolListenerWrapperProtocolFilterWrapper 其實都包括 SPI 介面 Protocol的構造器。所以創建出來的物件如上所以。關於 dubbo 的SPI機制可以參看 – 2.dubbo原始碼分析 之 核心SPI實現.

  • ProtocolListenerWrapper : 通過 SPI 機制獲取到 dubbo 的自定義擴充套件 InvokerListener
  • ProtocolFilterWrapper : 通過 SPI 機制獲取到 dubbo 的自定義擴充套件 Filter(常用)。

其實關於 dubbo 通過 SPI 機制獲取機制獲取到 dubbo 的自定義擴充套件 Filter,還蠻複雜與重要的。在這裡就不多說了,感興趣的朋友可以下去參看原始碼。

最終就到了InjvmProtocol暴露服務,其實它就是建立一個 InjvmExporter 物件返回。裡面包括 4 個屬性:

  • invoker:Invoker 物件例項
  • key:服務 key 值,在進行服務呼叫的時候會根據這個 key 值。獲取到當前服務暴露生成的 Exporter 物件。
  • exporterMap:服務 key 值與當前 Exporter 的對映

最後這個生成的 Exporter 最終會返回新增到 ServiceConfigexporters 屬性當中去。這樣就完成了 dubbo 服務暴露的本地暴露的過程。

這裡寫圖片描述

其實整個 dubbo 的本地暴露邏輯還是蠻簡單的,把它分開來主要還是整個服務暴露過程比較複雜。先給大家講解一下本地暴露。如果大家清楚了本地暴露,然後再理解遠端暴露就會更加容易。