30萬行的框架程式碼,這樣給Dubbo加擴充套件

語言: CN / TW / HK

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

一定要讀的原文地址:http://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控制檯日誌列印資訊

新增的序列化擴充套件執行成功了!原始碼在這裡,有興趣的朋友可下載自執行:

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

四、結語

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

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

最後附上原文連結!⏬⏬⏬
http://mp.weixin.qq.com/s?__biz=MzIwMDEzOTYzNA……