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標註的就快取起來
下課