Java SPI 與 Dubbo SPI
SPI(Service Provider Interface)是JDK內置的一種服務提供發現機制。本質是將介面實現類的全限定名配置在文件中,並由服務載入器讀取配置文件,載入實現類。這樣可以在運行時,動態為介面替換實現類。
在Java中SPI是被用來設計給服務提供商做插件使用的。基於策略模式來實現動態載入的機制。我們在程式只定義一個介面,具體的實現交個不同的服務提供者;在程式啟動的時候,讀取配置文件,由配置確定要調用哪一個實現。有很多組件的實現,如日誌、資料庫訪問等都是採用這樣的方式,最常用的就是 JDBC 驅動。
1. Java SPI
核心類:java.util.ServiceLoader
服務是一組眾所周知的介面和(通常是抽象的)類。服務提供者是服務的特定實現。提供者中的類通常實現介面,並子類化服務本身中定義的類。服務提供者可以以擴展的形式安裝在Java平台的實現中,即放置在任何常見擴展目錄中的jar文件。提供程式也可以通過將它們添加到應用程式的類路徑或其他特定於平台的方法來提供。
通過在資源目錄META-INF/services中放置一個提供程式配置文件來識別服務提供程式。文件名是服務類型的完全限定二進位名稱。該文件包含具體提供程式類的完全限定二進位名的列表,每行一個。每個名稱周圍的空格和製表符以及空白行將被忽略。注釋字元是’#’;在每一行中,第一個注釋字元之後的所有字元都將被忽略。文件必須用UTF-8編碼。
按照上面的方法,我們來寫個例子試一下
首先,定義一個介面Car
package org.example; public interface Car { void run(); }
兩個實現類
ToyotaCar.java
package org.example; public class ToyotaCar implements Car { @Override public void run() { System.out.println("Toyota"); } }
HondaCar.java
package org.example; public class HondaCar implements Car { @Override public void run() { System.out.println("Honda"); } }
在META-INF/services下創建一個名為org.example.Car的文本文件
org.example.ToyotaCar org.example.HondaCar
最後,寫個測試類運行看一下效果
package org.example; import java.util.ServiceLoader; public class App { public static void main( String[] args ) { ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class); serviceLoader.forEach(x->x.run()); } }
跟一下ServiceLoader的程式碼,看看是怎麼找到服務實現的
用當前執行緒的類載入器載入
介面和類載入器都有了,萬事俱備只欠東風
Java SPI 不足之處:
- 不能按需載入。Java SPI在載入擴展點的時候,會一次性載入所有可用的擴展點,很多是不需要的,會浪費系統資源
- 獲取某個實現類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據某個參數來獲取對應的實現類
- 不支援AOP與IOC
- 如果擴展點載入失敗,會導致調用方報錯,導致追蹤問題很困難
2. Dubbo SPI
Dubbo重新實現了一套功能更強的SPI機制, 支援了AOP與依賴注入,並且利用快取提高載入實現類的性能,同時支援實現類的靈活獲取。
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.8</version> </dependency>
核心類:org.apache.dubbo.common.extension.ExtensionLoader
先來了解一下@SPI註解,@SPI是用來標記介面是一個可擴展的介面
改造一下前面的例子,在Car介面上加上@SPI註解
package org.example; import org.apache.dubbo.common.extension.SPI; @SPI public interface Car { void run(); }
兩個實現類不變
在META-INF/dubbo目錄下創建名為org.example.Car的文本文件,內容如下(鍵值對形式):
toyota=org.example.ToyotaCar honda=org.example.HondaCar
編寫測試類
package org.example; import org.apache.dubbo.common.extension.ExtensionLoader; import java.util.ServiceLoader; public class App { public static void main( String[] args ) { // Java SPI ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class); serviceLoader.forEach(x->x.run()); // Dubbo SPI ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); Car car = extensionLoader.getExtension("honda"); car.run(); } }
下面跟一下程式碼
如果快取Map中有,直接返回,沒有則載入完以後放進去
載入策略到底是怎樣的呢?
到這裡就有點明白了,又看到了熟悉的ServiceLoad.load(),這不是剛才講的Java SPI嘛
回到之前策略那個地方,將策略按順序排列,依次遍歷所有的策略來載入。就是在那三個目錄下查找指定的文件,並讀取其中的內容
跟之前的ServiceLoader如出一轍
遇到@Adaptive標註的就快取起來
下課