什么是 SPI
SPI是Service Provider Interface的簡稱,是JDK默認提供的一種將接口和實現類進行分離的機制。這種機制能將接口和實現進行解耦,大大提升系統的可擴展性。
SPI機制約定:當一個Jar包需要提供一個接口的實現類時,這個Jar包需要在META-INF/services/目錄里同時創建一個以服務接口命名的文件。該文件里就是實現該服務接口的具體實現類。而當外部程序裝配這個模塊的時候,就能通過該Jar包META-INF/services/里的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。
比如下面的列子,jcl-over-slf4j
這個Jar包提供了conmon-logging中LogFactory
這個接口的實現。
文件中的內容如下:
# 這里表名具體的實現類是`org.apache.commons.logging.impl.SLF4JLogFactory`這個類
org.apache.commons.logging.impl.SLF4JLogFactory
# Axis gets at JCL through its own mechanism as defined by Commons Discovery, which
# in turn follows the instructions found at:
# http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service Provider
JDK為了方便查找服務的實現,還提供了一個工具類:java.util.ServiceLoader。
ServiceLoader<Object> loader = ServiceLoader.load(LogFactory);
loader.forEach((item)->{
System.out.println(item);
});
上面代碼中使用ServiceLoader
遍歷使用SPI機制提供的所有LogFactory
實現。
應用場景
SPI機制的主要應用有框架擴展和組件的替換等,比如
- JDBC接口實現類的運行時加載:我們連接具體的數據庫是都需要添加相關的Jar包依賴,但是不需要我們再做任何其他配置,只要將Jar包放到classpath下就行了。這是一個最常見的SPI應用場景。
- 日志門面加載具體的日志實現類:之前的博客中介紹到,jcl和slf4j等只是日志實現類,Log4j和LOgBack才是具體的日志實現。JCL和SLF4J加載日志實現類時也使用了SPI機制,具體請看上面章節中舉的列子。
- Spring中大量使用了SPI:比如對servlet3.0規范對ServletContainerInitializer的實現、自動類型轉換Type Conversion SPI(Converter SPI、Formatter SPI)等
自己實現
下面就一步步從定義接口到提供SPI實現類來演示下SPI機制具體的使用方式。
step1:先定義一個接口
public interface SaySomething {
String say(String name);
}
step2:編寫實現類
public class ASaySomething implements SaySomething {
@Override
public String say(String name) {
return "Hi,"+name+", l am A...";
}
}
step3:在resource下添加META-INFO/services目錄
添加完這個目錄后,添加一個以SaySomething
接口的全限定名為名字的文件,這個文件的內容是你要設置的具體實現類。這邊我們就設置實現類為上面的ASaySomething
。
step4:使用SPI機制
public static void main(String[] args) {
ServiceLoader<SaySomething> loader = ServiceLoader.load(SaySomething.class);
loader.forEach(item ->{item.say("csx");});
}
API和SPI的比較
在開發中我們還經常會提到API這個名詞,下面也總結下兩者的區別:
-
API (Application Programming Interface)在大多數情況下,都是實現方制定接口並完成對接口的實現,調用方僅僅依賴接口調用,且無權選擇不同實現。 從使用人員上來說,API 直接被應用開發人員使用。
-
SPI (Service Provider Interface)是調用方來制定接口規范,提供給外部來實現,調用方在調用時則選擇自己需要的外部實現。 從使用人員上來說,SPI 被框架擴展人員使用。
優缺點
優點
- 使用Java SPI機制的優勢是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起。應用程序可以根據實際業務情況啟用框架擴展或替換框架組件
缺點
- SPI必須先將接口的所有實現類都遍歷出來才能最后選擇具體使用哪個類。有些不要的類也會被實例化,可能會比較浪費內存。
ServiceLoader
並不是線程安全的。