1.SPI 是什么?
SPI 的全稱是 Service Provider Interface, 即提供服務接口;是一種服務發現機制,SPI 的本質是將接口實現類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現類。這樣可以在運行時,動態為接口替換實現類。正因此特性,我們可以很容易的通過 SPI 機制為我們的程序提供拓展功能。 如下圖:
系統設計的各個抽象,往往有很多不同的實現方案,在面對象設計里,一般推薦模塊之間基于接口編程,模塊之間不對實現硬編碼,一旦代碼涉及具體的實現類,就違反了可插拔的原則。Java SPI 就是提供這樣的一個機制,為某一個接口尋找服務的實現,有點類似 IOC 的思想,把裝配的控制權移到程序之外,在模塊化涉及里面這個各尤為重要。與其說 SPI 是 java 提供的一種服務發現機制,倒不如說是一種解耦思想。
2. 使用場景
數據庫驅動加載接口實現類的加載;如:JDBC 加載 Mysql,Oracle...
日志門面接口實現類加載,如:SLF4J 對 log4j、logback 的支持
Spring 中大量使用了 SPI,特別是 spring-boot 中自動化配置的實現
Dubbo 也是大量使用 SPI 的方式實現框架的擴展,它是對原生的 SPI 做了封裝,允許用戶擴展實現 Filter 接口。
3. 使用介紹
要使用 Java SPI,需要遵循以下約定:
當服務提供者提供了接口的一種具體實現后,需要在 JAR 包的 META-INF/services 目錄下創建一個以 “接口全限制定名” 為命名的文件,內容為實現類的全限定名;
接口實現類所在的 JAR 放在主程序的 classpath 下,也就是引入依賴。
主程序通過 java.util.ServiceLoder 動態加載實現模塊,它會通過掃描 META-INF/services 目錄下的文件找到實現類的全限定名,把類加載值 JVM, 并實例化它;
SPI 的實現類必須攜帶一個不帶參數的構造方法。
示例:
spi-interface 模塊定義
定義一組接口:public interface MyDriver
spi-jd-driver
spi-ali-driver
實現為:public class JdDriver implements MyDriver public class AliDriver implements MyDriver
在 src/main/resources/ 下建立 /META-INF/services 目錄, 新增一個以接口命名的文件 (org.MyDriver 文件)
內容是要應用的實現類分別 com.jd.JdDriver 和 com.ali.AliDriver

spi-core
一般都是平臺提供的核心包,包含加載使用實現類的策略等等,我們這邊就簡單實現一下邏輯:a. 沒有找到具體實現拋出異常 b. 如果發現多個實現,分別打印
public void invoker(){
ServiceLoader serviceLoader = ServiceLoader.load(MyDriver.class);
Iterator drivers = serviceLoader.iterator();
boolean isNotFound = true;
while (drivers.hasNext()){
isNotFound = false;
drivers.next().load();
}
if(isNotFound){
throw new RuntimeException("一個驅動實現類都不存在");
}
}
spi-test
public class App
{
public static void main( String[] args )
{
DriverFactory factory = new DriverFactory();
factory.invoker();
}
}
1. 引入 spi-core 包,執行結果

2. 引入 spi-core,spi-jd-driver 包
3. 引入 spi-core,spi-jd-driver,spi-ali-driver
4. 原理解析
看看我們剛剛是怎么拿到具體的實現類的? 就兩行代碼:
ServiceLoaderserviceLoader = ServiceLoader.load(MyDriver.class); Iterator drivers = serviceLoader.iterator();
所以,首先我們看 ServiceLoader 類:
public final class ServiceLoaderimplements Iterable{ //配置文件的路徑 private static final String PREFIX = "META-INF/services/"; // 代表被加載的類或者接口 private final Classservice; // 用于定位,加載和實例化providers的類加載器 private final ClassLoader loader; // 創建ServiceLoader時采用的訪問控制上下文 private final AccessControlContext acc; // 緩存providers,按實例化的順序排列 private LinkedHashMapproviders = new LinkedHashMap<>(); // 懶查找迭代器,真正加載服務的類 private LazyIterator lookupIterator; //服務提供者查找的迭代器 private class LazyIterator implements Iterator { ..... private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { //全限定名:com.xxxx.xxx String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class> c = null; try { //通過反射獲取 c = Class.forName(cn, false, loader); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } } ........
大概的流程就是下面這張圖:

應用程序調用 ServiceLoader.load 方法
應用程序通過迭代器獲取對象實例,會先判斷 providers 對象中是否已經有緩存的示例對象,如果存在直接返回
如果沒有存在,執行類轉載讀取 META-INF/services 下的配置文件,獲取所有能被實例化的類的名稱,可以跨越 JAR 獲取配置文件通過反射方法 Class.forName () 加載對象并用 Instance () 方法示例化類將實例化類緩存至 providers 對象中,同步返回。
5. 總結
優點:解耦
SPI 的使用,使得第三方服務模塊的裝配控制邏輯與調用者的業務代碼分離,不會耦合在一起,應用程序可以根據實際業務情況來啟用框架擴展和替換框架組件。
SPI 的使用,使得無須通過下面幾種方式獲取實現類
代碼硬編碼 import 導入
指定類全限定名反射獲取,例如 JDBC4.0 之前;Class.forName("com.mysql.jdbc.Driver")
缺點:
雖然 ServiceLoader 也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載并實例化一遍。如果你并不想用某些實現類,它也被加載并實例化了,這就造成了浪費。獲取某個實現類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據某個參數來獲取對應的實現類。
6. 對比

審核編輯:劉清
-
JAVA
+關注
關注
20文章
3001瀏覽量
116422 -
SPI
+關注
關注
17文章
1885瀏覽量
101227 -
JDBC
+關注
關注
0文章
25瀏覽量
13853
原文標題:可插拔組件設計機制 —SPI
文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
Java的SPI機制詳解
TE Connectivity推出插拔式 I/O 電纜組件
TE推出插拔式 I O 電纜組件產品介紹-赫聯電子
聊聊Dubbo - Dubbo可擴展機制實戰
空間受限應用中的PMBus熱插拔電路基礎介紹
XML在可重構制造執行系統組件管理中的應用
嵌入式Linux下可插拔輸入驅動機制研究
Zelio Relay可插拔式中間繼電器的介紹及其各型號的介紹
JDK內置的一種服務SPI機制
基于spring的SPI擴展機制是如何實現的?
Java、Spring、Dubbo三者SPI機制的原理和區別
PCIe熱插拔機制介紹
可插拔組件設計機制—SPI介紹
評論