SPI 全稱為Service Provider Interface,是一種服務發現機制。SPI 的本質是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載實現類。
這樣可以在運行時,動態為接口替換實現類。正因此特性,我們可以很容易的通過 SPI 機制為我們的程序提供拓展功能。
當服務的提供者提供了一種接口的實現之后,需要在 Classpath 下的 META-INF/services/ 目錄里創建一個以服務接口命名的文件,此文件記錄了該 jar 包提供的服務接口的具體實現類。當某個應用引入了該 jar 包且需要使用該服務時,JDK SPI 機制就可以通過查找這個 jar 包的 META-INF/services/ 中的配置文件來獲得具體的實現類名,進行實現類的加載和實例化,最終使用該實現類完成業務功能。
簡單點說SPI就是 JDK 內置的一個服務發現機制,它使得接口和具體實現完全解耦。我們只聲明接口,具體的實現類在配置中選擇。
具體的就是你定義了一個接口,然后在META-INF/services
目錄下放置一個與接口同名的文本文件,文件的內容為接口的實現類,多個實現類用換行符分隔。
這樣就通過配置來決定具體用哪個實現!
例如,使用 Java 語言訪問數據庫時我們會使用到 java.sql.Driver 接口,不同數據庫產品底層的協議不同,提供的 java.sql.Driver 實現也不同,在開發 java.sql.Driver 接口時,開發人員並不清楚用戶最終會使用哪個數據庫,在這種情況下就可以使用 Java SPI 機制在實際運行過程中,為 java.sql.Driver 接口尋找具體的實現。
下面我們通過一個簡單的示例演示下 JDK SPI 的基本使用方式:
首先我們需要創建一個 Log 接口,來模擬日志打印的功能:
public interface Log { void log(String info); }
接下來提供兩個實現—— Logback 和 Log4j,分別代表兩個不同日志框架的實現,如下所示:
public class Logback implements Log { @Override public void log(String info) { System.out.println("Logback:" + info); } } public class Log4j implements Log { @Override public void log(String info) { System.out.println("Log4j:" + info); } }
在項目的 resources/META-INF/services 目錄下添加一個名為 com.xxx.Log 的文件,這是 JDK SPI 需要讀取的配置文件,具體內容如下:
com.xxx.impl.Log4j
com.xxx.impl.Logback
最后創建 main() 方法,其中會加載上述配置文件,創建全部 Log 接口實現的實例,並執行其 log() 方法,如下所示:
public class Main { public static void main(String[] args) { ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class); Iterator<Log> iterator = serviceLoader.iterator(); while (iterator.hasNext()) { Log log = iterator.next(); log.log("JDK SPI"); }
// JDK8 Lamda表達式的forEach遍歷寫法
// serviceLoader.forEach(log -> log.log("hello")); } } // 輸出如下: // Log4j:JDK SPI // Logback:JDK SPI