SPI(Service Provider Interfaces),中文直譯服務提供者介面,一種服務發現機制。可能很多人都不太熟悉這個機制,但是平常或多或少都用到了這個機制,比如我們使用 JDBC 連接操作資料庫的時候。 SPI 主要適用於功能擴展的場景,如一些框架提供某一部分功能可以由第三方開發人員 ...
SPI(Service Provider Interfaces),中文直譯服務提供者介面,一種服務發現機制。可能很多人都不太熟悉這個機制,但是平常或多或少都用到了這個機制,比如我們使用 JDBC 連接操作資料庫的時候。
SPI 主要適用於功能擴展的場景,如一些框架提供某一部分功能可以由第三方開發人員擴展,滿足其自身業務需求。
假設我們在公司內實現了一個統一登陸框架,框架內部僅僅提供用戶名/密碼登陸方式。後來 A 部門想使用該框架,但是他們想增加微信登陸授權。正常情況下,我們可以改動登陸框架代碼,增加微信登陸實現方式。如果後面又增加 QQ 登陸,淘寶登陸那?也只能不斷相應的實現。
SPI 實現方式
這種情況如果使用 SPI,可以在不用改動框架代碼前提下,增加新的登陸實現方式。下麵用代碼演示如何使用 SPI。
定義介面
首先我們新建一個 maven 項目 oauth-api
,在這個項目創建一個公共介面。
public interface OauthLoginService {
void login();
}
第三方實現該介面
再新建一個 maven 項目 wechat-oauth
,引入上面 oauth-api
依賴
public class WechatLoginService implements OauthLoginService {
@Override
public void login() {
System.out.println("使用微信登陸授權");
}
}
定義配置文件
SPI 需要將介面實現定義在配置文件中,文件名為介面全名稱,如 com.andyxh.OauthLoginService
,配置文件需放在 resources\META-INF\services 文件夾下。文件內容如下:
com.another.WechatLoginService
載入介面實現類
新建 maven 項目 oauth-login
,在這個項目中引入 wechat-oauth
與 oauth-api
依賴。SPI 核心將會使用 java.util.ServiceLoader
讀取上面上面定義配置文件,載入所有服務實現類。使用代碼如下:
ServiceLoader<OauthLoginService> serviceLoader=ServiceLoader.load(OauthLoginService.class);
serviceLoader.forEach(OauthLoginService::login);
列印結果:
使用微信登陸授權
SPI 實際應用
上面說過 JDBC 中使用到 SPI 進位。 JDK 定義標準資料庫介面,相應的資料庫廠商實現這類介面。以 mysql-connector-javal
為例。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
mysql jar 包 META-INF/services 中存在java.sql.Driver
文件,這個文件定義了實現類。
com.mysql.cj.jdbc.Driver
可以看到 java.sql.Driver
是標準 SPI 介面,而 com.mysql.cj.jdbc.Driver
是 mysql 標準實現介面。
何時載入 java.sql.Driver
?
我們將會使用 DriverManager.getConnection
獲取相應資料庫連接。這個類內部存在一個靜態代碼塊,將會使用 ServiceLoader
載入實現類。
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
....
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
....
}
Java SPI 存在問題
ServiceLoader
一次性將會實例化所有實現,但是如果沒有某一擴展初始化耗時很久,但是卻不需要立刻使用,就會非常浪費資源。
基於這個問題, Dubbo SPI 機制改進 Java SPI 的不足,做到按需載入並且增加 ioc 與 aop 的功能,下篇文章可以在具體聊聊,敬請期待。