SPI 機制應用在了大家項目中的很多地方,在很多框架中也有普遍應用,只不過很多人並沒有感知。
舉個例子,為什麼我們在項目中引入 mysql-connector 的 jar 包,就可以直接連接 MySQL 數據庫了?
本篇文章就來介紹一下 SPI,聊聊 Java 、Spring、Dubbo 中的 SPI 機制。
SPI
SPI ( Service Provider Interface),是一種服務發現機制。
SPI 的本質是將接口的實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載對應接口的實現類。這樣就可以在運行時,獲取接口的實現類。
通過這一特性,我們可以很容易地通過 SPI 機制為程序提供拓展功能。
Java SPI 是“基於接口的編程+策略模式+配置文件”組合實現的動態加載機制。
設計一個接口,將接口的實現類寫在配置文件中,服務通過讀取配置文件來發現實現類,進行加載實例化然後使用。
配置文件路徑:classpath下的META-INF/services/
配置文件名:接口的全限定名
配置文件內容:接口的實現類的權限定名
應用舉例:
1、自定義接口
// 接口
public interface Superman {
void introduce();
}
// 實現類1
public class IronMan implements Superman{
@Override
public void introduce() {
System.out.println("我是鋼鐵俠!");
}
}
// 實現類2
public class CaptainAmerica implements Superman {
@Override
public void introduce() {
System.out.println("我是美國隊長!");
}
}
Java SPI 總結:
Java SPI 機制:為某個接口發現/尋找服務實現的機制。
優點:
核心思想:解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離。可以根據實際業務情況進行使用或擴展。
缺點:
1、獲取接口的實現類的方式不靈活
serviceLoader 只能通過 Iterator 形式遍歷獲取,不能根據參數獲取指定的某個實現類。
2、資源浪費
serviceLoader 只能通過遍歷的方式將接口的實現類全部獲取、加載並實例化一遍。如果不想用某些實現類,它也會被加載並實例化,造成浪費。
Spring SPI
與 JDK SPI 類似, 相對於 Java 的 SPI 的優勢在於:
Spring SPI 指定配置文件為 classpath 下的 META-INF/spring.factories,所有的拓展點配置放到一個文件中。
配置文件內容為 key-value 類型,key 為接口的全限定名, value 為實現類的全限定名,可以為多個。
以 EnableAutoConfiguration 的實現類 DubboAutoConfiguration 為例:
在 spring boot 啓動過程中 ,在 SpringFactoriesLoader.loadFactoryNames(type, classLoader) 這一步中會將 EnableAutoConfiguration 的實現類全部進行加載、解析、初始化。
在實例化 EnableAutoConfiguration 的實現類時,會執行實現類 dubboAutoConfiguration 中的具體邏輯,將 dubbo 服務器啓動並註冊到 spring 容器中。
DubboAutoConfiguration的大概實現:
讀取配置文件中的配置項值(配置項:DubboConfigConfiguration)生成多個配置 bean,掃描 dubbo @Service 和 @Reference 註解的類,生成對應的 bean。
其實在我們使用的第三方依賴包中,很多都使用了 Spring SPI,如 dubbo,mybatis,redisson 等等。
Dubbo SPI
dubbo的 Filter、Protocol、Cluster、LoadBalance 等都是通過 SPI 的方式進行拓展加載的。
特點:
1、dubbo SPI 為每個拓展點(接口)單獨設置一個文件,文件名為接口的全限定名。如org.apache.dubbo.rpc.Filter,org.apache.dubbo.rpc.Protocol,org.apache.dubbo.rpc.cluster.LoadBalance 等。