什麼是 Java SPI
“Java SPI(Service Provider Interface),是JDK內置的一種動態加載擴展點的實現,是一種服務機制,SPI 的本質是將接口實現類的全限定名配置在文件中,並由服務加載機器讀取配置文件,加載實現類,這樣就是可以在運行時動態的爲接口替換實現類” ------ dubbo 官網
代碼實現
首先在ClassPath的META-INF/services目錄下放置一個與接口同名的文本文件,文件的內容爲接口的實現類,多個實現類用換行符分隔。JDK中使用java.util.ServiceLoader來加載具體的實現。
1.首先定義一個RobotCar接口
/**
* @Auther: corey
* @Date: 2020/7/3 10:05
* @Description: qq飛車
*/
public interface RobotCar {
void hello();
}
2.提供RobotCar的實現, RobotCar有兩個實現。BigPenguin和OldGodmother。
/**
* @Auther: corey
* @Date: 2020/7/3 10:06
* @Description: 大企鵝
*/
public class BigPenguin implements RobotCar {
@Override
public void hello() {
System.out.println("Hello 我是大企鵝。。。");
}
}
/**
* @Auther: corey
* @Date: 2020/7/3 10:08
* @Description: 老乾媽
*/
public class OldGodmother implements RobotCar {
@Override
public void hello() {
System.out.println("hello 我是老幹ma。。。");
}
}
3.添加配置文件 在META-INF/services目錄添加一個文件,文件名和接口全名稱相同,所以文件是META-INF/services/com.service.RobotCar文件內容爲:
service.impl.BigPenguin
service.impl.OldGodmother
4.通過ServiceLoader加載RobotCar實現
/**
* @Auther: corey
* @Date: 2020/7/3 10:17
* @Description:
*/
public class RobotCarTest {
public static void main(String[] args) {
ServiceLoader<RobotCar> serviceLoader = ServiceLoader.load(RobotCar.class);
System.out.println("Java SPI....");
serviceLoader.forEach(RobotCar::hello);
}
}
運行結果爲:
在上面的例子中,我們定義了一個擴展點和它的兩個實現。在ClassPath中添加了擴展的配置文件,最後使用ServiceLoader來加載所有的擴展點。 最終的輸出結
總結:
如圖所示:
Java SPI 實際上是“基於接口的編程+策略模式+配置文件”組合實現的動態加載機制。系統設計的各個抽象,往往有很多不同的實現方案,在面向的對象的設計裏,一般推薦模塊之間基於接口編程,模塊之間不對實現類進行硬編碼。一旦代碼裏涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。爲了實現在模塊裝配的時候能不在程序裏動態指明,這就需要一種服務發現機制。
Java SPI就是提供這樣的一個機制:爲某個接口尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。所以SPI的核心思想就是解耦。
優點:
使用Java SPI機制的優勢是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起。應用程序可以根據實際業務情況啓用框架擴展或替換框架組件。
缺點:
-
雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載並實例化一遍。如果你並不想用某些實現類,它也被加載並實例化了,這就造成了浪費。獲取某個實現類,的方式不夠靈活
-
多個併發多線程使用ServiceLoader類的實例是不安全的。
使用場景常見地方有
-
數據庫驅動加載接口實現類的加載
JDBC加載不同類型數據庫的驅動 -
日誌門面接口實現類加載,SLF4J加載不同提供商的日誌實現類
-
Spring中大量使用了SPI,比如:對servlet3.0規範對ServletContainerInitializer的實現、自動類型轉換Type Conversion SPI(Converter SPI、Formatter SPI)等
-
Dubbo中也大量使用SPI的方式實現框架的擴展, 不過它對Java提供的原生SPI做了封裝,允許用戶擴展實現Filter接口
參考:
http://dubbo.apache.org/zh-cn/blog/introduction-to-dubbo-spi.html
https://developer.aliyun.com/article/640161