1.SPI 是什么?
SPI 的全稱是 Service Provider Interface, 即提供服務(wù)接口;是一種服務(wù)發(fā)現(xiàn)機制,SPI 的本質(zhì)是將接口實現(xiàn)類的全限定名配置在文件中,并由服務(wù)加載器讀取配置文件,加載實現(xiàn)類。這樣可以在運行時,動態(tài)為接口替換實現(xiàn)類。正因此特性,我們可以很容易的通過 SPI 機制為我們的程序提供拓展功能。 如下圖:
系統(tǒng)設(shè)計的各個抽象,往往有很多不同的實現(xiàn)方案,在面對象設(shè)計里,一般推薦模塊之間基于接口編程,模塊之間不對實現(xiàn)硬編碼,一旦代碼涉及具體的實現(xiàn)類,就違反了可插拔的原則。Java SPI 就是提供這樣的一個機制,為某一個接口尋找服務(wù)的實現(xiàn),有點類似 IOC 的思想,把裝配的控制權(quán)移到程序之外,在模塊化涉及里面這個各尤為重要。與其說 SPI 是 java 提供的一種服務(wù)發(fā)現(xiàn)機制,倒不如說是一種解耦思想。
2. 使用場景
數(shù)據(jù)庫驅(qū)動加載接口實現(xiàn)類的加載;如:JDBC 加載 Mysql,Oracle...
日志門面接口實現(xiàn)類加載,如:SLF4J 對 log4j、logback 的支持
Spring 中大量使用了 SPI,特別是 spring-boot 中自動化配置的實現(xiàn)
Dubbo 也是大量使用 SPI 的方式實現(xiàn)框架的擴展,它是對原生的 SPI 做了封裝,允許用戶擴展實現(xiàn) Filter 接口。
3. 使用介紹
要使用 Java SPI,需要遵循以下約定:
當(dāng)服務(wù)提供者提供了接口的一種具體實現(xiàn)后,需要在 JAR 包的 META-INF/services 目錄下創(chuàng)建一個以 “接口全限制定名” 為命名的文件,內(nèi)容為實現(xiàn)類的全限定名;
接口實現(xiàn)類所在的 JAR 放在主程序的 classpath 下,也就是引入依賴。
主程序通過 java.util.ServiceLoder 動態(tài)加載實現(xiàn)模塊,它會通過掃描 META-INF/services 目錄下的文件找到實現(xiàn)類的全限定名,把類加載值 JVM, 并實例化它;
SPI 的實現(xiàn)類必須攜帶一個不帶參數(shù)的構(gòu)造方法。
示例:
spi-interface 模塊定義
定義一組接口:public interface MyDriver
spi-jd-driver
spi-ali-driver
實現(xiàn)為:public class JdDriver implements MyDriver public class AliDriver implements MyDriver
在 src/main/resources/ 下建立 /META-INF/services 目錄, 新增一個以接口命名的文件 (org.MyDriver 文件)
內(nèi)容是要應(yīng)用的實現(xiàn)類分別 com.jd.JdDriver 和 com.ali.AliDriver
spi-core
一般都是平臺提供的核心包,包含加載使用實現(xiàn)類的策略等等,我們這邊就簡單實現(xiàn)一下邏輯:a. 沒有找到具體實現(xiàn)拋出異常 b. 如果發(fā)現(xiàn)多個實現(xiàn),分別打印
public void invoker(){ ServiceLoaderserviceLoader = ServiceLoader.load(MyDriver.class); Iterator drivers = serviceLoader.iterator(); boolean isNotFound = true; while (drivers.hasNext()){ isNotFound = false; drivers.next().load(); } if(isNotFound){ throw new RuntimeException("一個驅(qū)動實現(xiàn)類都不存在"); } }
spi-test
public class App { public static void main( String[] args ) { DriverFactory factory = new DriverFactory(); factory.invoker(); } }
1. 引入 spi-core 包,執(zhí)行結(jié)果
2. 引入 spi-core,spi-jd-driver 包
3. 引入 spi-core,spi-jd-driver,spi-ali-driver
4. 原理解析
看看我們剛剛是怎么拿到具體的實現(xiàn)類的? 就兩行代碼:
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; // 創(chuàng)建ServiceLoader時采用的訪問控制上下文 private final AccessControlContext acc; // 緩存providers,按實例化的順序排列 private LinkedHashMapproviders = new LinkedHashMap<>(); // 懶查找迭代器,真正加載服務(wù)的類 private LazyIterator lookupIterator; //服務(wù)提供者查找的迭代器 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; } } ........
大概的流程就是下面這張圖:
應(yīng)用程序調(diào)用 ServiceLoader.load 方法
應(yīng)用程序通過迭代器獲取對象實例,會先判斷 providers 對象中是否已經(jīng)有緩存的示例對象,如果存在直接返回
如果沒有存在,執(zhí)行類轉(zhuǎn)載讀取 META-INF/services 下的配置文件,獲取所有能被實例化的類的名稱,可以跨越 JAR 獲取配置文件通過反射方法 Class.forName () 加載對象并用 Instance () 方法示例化類將實例化類緩存至 providers 對象中,同步返回。
5. 總結(jié)
優(yōu)點:解耦
SPI 的使用,使得第三方服務(wù)模塊的裝配控制邏輯與調(diào)用者的業(yè)務(wù)代碼分離,不會耦合在一起,應(yīng)用程序可以根據(jù)實際業(yè)務(wù)情況來啟用框架擴展和替換框架組件。
SPI 的使用,使得無須通過下面幾種方式獲取實現(xiàn)類
代碼硬編碼 import 導(dǎo)入
指定類全限定名反射獲取,例如 JDBC4.0 之前;Class.forName("com.mysql.jdbc.Driver")
缺點:
雖然 ServiceLoader 也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現(xiàn)類全部加載并實例化一遍。如果你并不想用某些實現(xiàn)類,它也被加載并實例化了,這就造成了浪費。獲取某個實現(xiàn)類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據(jù)某個參數(shù)來獲取對應(yīng)的實現(xiàn)類。
6. 對比
審核編輯:劉清
-
JAVA
+關(guān)注
關(guān)注
20文章
2984瀏覽量
106754 -
SPI
+關(guān)注
關(guān)注
17文章
1767瀏覽量
94512 -
JDBC
+關(guān)注
關(guān)注
0文章
25瀏覽量
13580
原文標題:可插拔組件設(shè)計機制 —SPI
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Java的SPI機制詳解

TE Connectivity推出插拔式 I/O 電纜組件
TE推出插拔式 I O 電纜組件產(chǎn)品介紹-赫聯(lián)電子
聊聊Dubbo - Dubbo可擴展機制實戰(zhàn)
空間受限應(yīng)用中的PMBus熱插拔電路基礎(chǔ)介紹
XML在可重構(gòu)制造執(zhí)行系統(tǒng)組件管理中的應(yīng)用
嵌入式Linux下可插拔輸入驅(qū)動機制研究
Zelio Relay可插拔式中間繼電器的介紹及其各型號的介紹
JDK內(nèi)置的一種服務(wù)SPI機制
基于spring的SPI擴展機制是如何實現(xiàn)的?
Java、Spring、Dubbo三者SPI機制的原理和區(qū)別

PCIe熱插拔機制介紹

評論