30萬行的框架代碼,這樣給Dubbo加擴展

語言: CN / TW / HK

本文首發於公眾號【看點代碼再上班】,建議關注公眾號,及時閲讀最新文章。

一定要讀的原文地址:https://mp.weixin.qq.com/s?__biz=MzIwMDEzOTYzNA……

大家好,我是tin,這是我的第18篇原創文章

Dubbo已是一個很大的項目,2.7.15版本中光Java代碼已累積到了30萬行。

Dubbo在國內微服務界的名氣不用我説大家應該都知道,它有很多的突出的設計點,其中,擴展機制就是一個。

今天結合實例講一講如何給Dubbo加擴展,先上一個目錄:

一、Dubbo的擴展機制

1. 開閉原則

2. Dubbo擴展

二、從擴展點到@SPI註解

1. 擴展點

2. @SPI註解

3. ExtensionLoader擴展加載類

三、自定義一個擴展

1. 包結構

2. 實現Serialization擴展點

3. META-INF目錄下配置擴展信息

4. 配置使用自實現的Jackson協議

5. 啟動客户端,完成Dubbo服務的jackson序列化

四、結語


一、Dubbo的擴展機制

1. 開閉原則

我們應該都聽説過一個非常重要的設計原則——開閉原則。在大項目編程設計中,這個原則顯得尤其重要。從定義來理解,開閉原則表示:

Software entities like classes,modules and functions should be open for extension but closed for modifications.(一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。)

用大白話來説就是,你可以往一個系統裏面加代碼功能,但是不修改系統原代碼邏輯。

在基本的六大原則中,開閉是最基礎的原則同時也是其他幾大原則的指導基石。如果用Java語言來描述,開閉原則是抽象類,其他幾大原則是具體的實現類。

開閉原則在面向對象設計領域中的地位就類似於牛頓第一定律在力學、勾股定律在幾何學、質能方程在狹義相對論中的地位,其地位無人能及

2. Dubbo擴展

Dubbo的擴展機制正是應用了這種設計原則思想,它設計定義了很多的接口(也叫擴展點),以及定義了一整套加載機制(SPI機制)和接口契約。

當需要對擴展點進行擴展的時候,只需新增擴展點的實現類以及配置指定配置文件即可

在Dubbo官網上可以看到已定義的SPI擴展有非常多:

由此也可見擴展對Dubbo的重要性。Dubbo採用的這種擴展結構對於Dubbo來説有兩大好處:

  1. 作為框架的維護者,在添加一個新功能時,只需要添加一些新代碼,而不用大量的修改現有的代碼,即符合開閉原則。
  2. 作為框架的使用者,在添加一個新功能時,不需要去修改框架的源碼,在自己的工程中添加代碼即可。

二、從擴展點到@SPI註解

1. 擴展點

要深入理解Dubbo的擴展機制,需要先理解兩個概念:

  1. 擴展點,也是一個Java接口,可以通過 SPI 機制查找並加載擴展實現的接口(又稱“擴展接口”)。比如上圖《SPI擴展實現》中的協議擴展接口org.apache.dubbo.rpc.Protocol,它就是一個擴展點。
  2. 擴展,擴展點的實現類。比如InjvmProtocol實現了Protocol,它就是一個擴展。

和Java SPI類似,Dubbo擴展也需要在指定的META-INF目錄下配置config文件,當服務啟動的時候,Dubbo則從相應路徑目錄加載並實例化擴展。

Dubbo 按照 SPI 配置文件的用途,將其分成了三類目錄:

  1. META-INF/services/ 目錄: 兼容 JDK SPI。
  2. META-INF/dubbo/ 目錄: 存放用户自定義的 SPI 配置文件。
  3. META-INF/dubbo/internal/ 目錄: 存放 Dubbo 框架內部使用的 SPI 配置文件。

就拿協議擴展點來説:org.apache.dubbo.rpc.Protocol

它的擴展實現InjvmProtocol是在dubbo-rpc-injvm包內實現的,在該包的META-INF/dubbo/internal/ 目錄下有對應的擴展配置。

配置命名形如 KV 的格式:

injvm=org.apache.dubbo.rpc.protocol.dubbo.InjvmProtocol

其中 key 被稱為擴展名(也就是 ExtensionName),當我們在為一個接口查找具體實現類時,可以指定擴展名來選擇相應的擴展實現。

就例如,這裏擴展名為 injvm,我們在xml文件配置服務Provider的時候,就可以指定protocol name為injvm,表示服務接口將使用injvm協議。

2. @SPI註解

@SPI註解作用於擴展點上,表明該接口是一個擴展點,可以被Dubbo的ExtensionLoader加載。前文中的 org.apache.dubbo.rpc.Protocol 接口就是一個擴展點:

@SPI 註解有一個 value 屬性值,用於指定默認的擴展名稱。

上文Protocol擴展點聲明中表示,在通過 Dubbo SPI 加載 Protocol 接口實現時,如果沒有明確指定擴展名,則默認使用dubbo作為該擴展點的實現,所以,我們的Dubbo服務接口默認是使用dubbo協議的。

3. ExtensionLoader擴展加載類

作用類似於Java SPI的java.util.ServiceLoader,負責擴展的加載和生命週期維護。

我們要看Dubbo擴展的源碼的話,ExtensionLoader類是非常關鍵的類,SPI相關的核心邏輯都在這個類中。

ExtensionLoader位於 dubbo-common 模塊的 extension 包中:

它有一個獲取指定擴展點的擴展加載器的方法getExtensionLoader:``

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } ​ ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }

和一個獲取指定擴展名的擴展的方法getExtension:

public T getExtension(String name) { return getExtension(name, true); }

所以,一般使用方式如下:

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("injvm");

三、自定義一個擴展

1. 包結構

Dubbo支持多種序列化方式,比如hessian、kryo、pb等。看源碼包dubbo-serialization可以看到不同的序列化方式實現原理:

今天,我不去説每個序列化方式的實現細節,只是通過引入jackson包實現給Dubbo新增一種序列化方式。

首先,我的包結構如下:

dubbo-demo-consumer: 定義Dubbo消費者客户端

dubbo-demo-facade: 定義接口

dubbo-demo-provider: 定義Dubbo提供者客户端

dubbo-demo-serialization: jackson序列化擴展

重點在dubbo-demo-serialization包,這個包是序列化擴展實現的關鍵。

2. 實現Serialization擴展點

Dubbo已經定義好序列化策略的擴展點Serialization(從@SPI註解可以看出是一個SPI擴展點)

首先,引入jackson序列化工具依賴包:

<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.0</version> </dependency>

我定義了JacksonSerialization擴展實現,如下:

3. META-INF目錄下配置擴展信息

根據Dubbo擴展實現的原理,Dubbo會從這個目錄加載擴展,我把key定義為jackson,如下:

4. 配置使用自實現的Jackson協議

我們可以在provider配置文件內指定我們的服務採用哪種序列化方式,比如採用xml文件方式可以指定dubbo:protocol標籤中的serialization屬性:

5. 啟動客户端,完成Dubbo服務的jackson序列化

首先啟動provider

其次啟動consumer

查看provider控制枱日誌打印信息

可以看到jason序列化擴展打印的相關read/write日誌:

查看consumer控制枱日誌打印信息

新增的序列化擴展運行成功了!源碼在這裏,有興趣的朋友可下載自運行:

https://github.com/iam-tin/tin-example

四、結語

我是tin,一個在努力讓自己變得更優秀的普通工程師。自己閲歷有限、學識淺薄,如有發現文章不妥之處,非常歡迎加我提出,我一定細心推敲並加以修改。

堅持創作不容易,你的正反饋是我堅持輸出的最強大動力,謝謝!

最後附上原文鏈接!⏬⏬⏬
https://mp.weixin.qq.com/s?__biz=MzIwMDEzOTYzNA……