SPI(Service Provider Interface)是JDK內置的一種服務提供發現機制。本質是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載實現類。這樣能夠在運行時,動態爲接口替換實現類。java
在Java中SPI是被用來設計給服務提供商作插件使用的。基於策略模式來實現動態加載的機制。咱們在程序只定義一個接口,具體的實現交個不一樣的服務提供者;在程序啓動的時候,讀取配置文件,由配置肯定要調用哪個實現。有不少組件的實現,如日誌、數據庫訪問等都是採用這樣的方式,最經常使用的就是 JDBC 驅動。數據庫
1. Java SPIapache
核心類:java.util.ServiceLoader緩存
服務是一組衆所周知的接口和(一般是抽象的)類。服務提供者是服務的特定實現。提供者中的類一般實現接口,並子類化服務自己中定義的類。服務提供者能夠以擴展的形式安裝在Java平臺的實現中,即放置在任何常見擴展目錄中的jar文件。提供程序也能夠經過將它們添加到應用程序的類路徑或其餘特定於平臺的方法來提供。ide
經過在資源目錄META-INF/services中放置一個提供程序配置文件來識別服務提供程序。文件名是服務類型的徹底限定二進制名稱。該文件包含具體提供程序類的徹底限定二進制名的列表,每行一個。每一個名稱周圍的空格和製表符以及空白行將被忽略。註釋字符是'#';在每一行中,第一個註釋字符以後的全部字符都將被忽略。文件必須用UTF-8編碼。性能
按照上面的方法,咱們來寫個例子試一下測試
首先,定義一個接口Car編碼
package org.example; public interface Car { void run(); }
兩個實現類spa
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 不足之處:
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標註的就緩存起來
下課