JAVA中的SPI思想介紹
1. SPI介紹
SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現或者擴展的接口,其意義在于為某個接口尋找服務的實現,主要應用在框架中用來尋找組件,提高擴展性。
汽車制造是一個比較繁瑣的過程,通常的手段是先規(guī)定汽車各個零部件的生產規(guī)格,各個零部件廠商按照這種規(guī)則去生產合格的零部件。汽車生產商挑選合適的零部件去組裝以出產汽車。汽車某個零部件損壞也不用廢棄掉整個汽車,只需要更換組件即可。
SPI就是用來怎么去尋找汽車零部件的一種機制,生產規(guī)格就是接口的定義,零部件生產商生產零部件就是遵循接口提供具體的實現,SPI挑選合適的組件進行組裝后完成特定的功能,當某個組件存在漏洞或問題時可以在不改動代碼的前提下替換組件以提高擴展性。
2. SPI規(guī)則
SPI旨在為某個接口尋找服務的實現,因此在設計初期就要規(guī)定好組件的接口,JAVA的SPI機制使用步驟如下:
定義組件接口(生產規(guī)格)
實現組件接口(提供組件)
配置組件文件(查找組件)
反射實例調用(組裝工作)
3. SPI案例

組件定義工程中定義組件開發(fā)的規(guī)范,即定義組件需要實現哪些接口組件A工程和組件B工程提供對組件的實現,即實現組件定義的接口組件應用工程挑選合適的組件進行組件的運用
3.1 組件的定義
在【commons-api】工程中定義組件的規(guī)范,即定義接口,接口名稱為ComponentService,內容如下:
public interface ComponentService
{
/**
* 獲取組件的名稱
* @return 組件名稱
*/
String getComponentName();
}
3.2 組件的實現
在【component-A】工程中按照組件定義的規(guī)范提供組件,即實現組件定義接口,類名稱為ComponentA,內容如下:
public class ComponentA implements ComponentService
{
/**
* 組件名稱
*/
private static final String COMPONENT_NAME = "組件A";
@Override
public String getComponentName()
{
return COMPONENT_NAME;
}
}
按照JAVA的SPI規(guī)則在【component-A】工程的resource/META-INF/services資源目錄下新建文件,文件名稱為組件接口的全限定名,內容為組件實現的全限定名

在【component-B】工程中也提供對應的組件實現,類名稱為ComponentB,內容如下:
public class ComponentB implements ComponentService
{
/**
* 組件名稱
*/
private static final String COMPONENT_NAME = "組件B";
@Override
public String getComponentName()
{
return COMPONENT_NAME;
}
}
同樣在【component-B】工程的resource/META-INF/services資源目錄下配置文件

3.3 組件的選用
基于maven構建的java工程使用pom文件來編排項目所需要的依賴組件,現在需要用到組件,并且我覺得A組件比B組件更適合我,如是在【component-application】工程的pom中我編排了組件A,內容如下:
<dependencies>
<dependency>
<groupId>com.xxxx</groupId>
<artifactId>component-A</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
在【component-application】工程中新建應用程序啟動類來運用組件,類名稱為Main,內容如下:
public class Main
{
public static void main(String[] args)
{
// 加載組件
ServiceLoader<ComponentService> components = ServiceLoader.load(ComponentService.class);
for (ComponentService component : components)
{
// 運用組件:打印組件名稱
System.out.println(component.getComponentName());
}
}
}
啟動【component-application】工程的Main類的main方法,結果如下:

假如某一天我發(fā)現組件A存在很大漏洞,需要更換組件將組件A替換成組件B,只需要在【component-application】工程的pom中去掉組件A的依賴,導入組件B的依賴即可,pom內容如下:
<dependencies>
<dependency>
<groupId>com.xxxx</groupId>
<artifactId>component-B</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
無需修改【component-application】工程的具體使用細節(jié),就可以達到替換組件的目的,運行如下:

4. SPI原理
JAVA提供的SPI機制主要依靠的是java.util.ServiceLoader類,從SPI案例中入手,進入ServiceLoader.load方法一探究竟。

load方法最終會創(chuàng)建一個LazyIterator 的實例,看到Iterator大概就知道和迭代器有關,繼續(xù)了解一下LazyIterator 是什么

猜想得不錯,LazyIterator 和迭代器有關,它是ServiceLoader的一個內部類,實現了Iterator接口,那只需要重點關注Iterator接口定義的方法
public boolean hasNext()
public S next()
Iterator接口定義的hasNext方法用于判斷迭代的是否是否還有下一個元素,LazyIterator 的hasNext方法最終是調用的hasNextService方法,重點研究這個。

通過類加載器獲取到指定目錄下的資源文件配置的組件實現的全限定名,存放在configs的一個容器變量中,有了組件實現的全限定名,后面多半就是反射生成對象返回了,繼續(xù)看一下LazyIterator 的next方法。LazyIterator的next方法主要邏輯是在nextService方法中,仔細分析一下

和剛才的猜想一致,拿到了組件實現的全限定名通過Class.forName來生成組件對象,所以程序就通過for循環(huán)得到了對象可以進行調用。
5. SPI要求
java.util.ServiceLoader類是這樣來描述自己的
A simple service-provider loading facility.
一個簡單的服務提供者加載工具
The only requirement enforced by this facility is thatprovider classes must have a zero-argument constructor so that they can beinstantiated during loading
強制執(zhí)行的唯一要求是提供者類必須有一個無參的構造函數,以便它們可以在加載過程中實例化,從Class.forName生成實例對象就可以看出使用的是無參構造
6. SPI應用
? SPI的這種組件替換思想很容易讓人想到我們熟知的JDBC規(guī)范。JDBC是JAVA規(guī)定的應用程序連接數據庫的標準,定義了連接數據庫的幾個接口,比如Connection、Statement、ResultSet。各個數據庫廠商提供自己的JDBC實現,這就是我們所說的數據庫驅動。開發(fā)人員只需要關心如何使用JDBC的各個接口,不需要學習不同廠商的實現,這就是面向接口編程。
JDBC編程分為四個步驟,SPI在驅動管理器DriverManager中得到了應用

Mysql驅動和SqlServer驅動都有SPI的配置

在驅動管理器的loadInitialDrivers方法中就會通過SPI機制獲取可用的驅動,loadInitialDrivers方法會在靜態(tài)代碼塊中被調用。這里并沒有通過全限定名反射實例化,真正的驅動注冊是數據庫廠商提供的驅動類中通過靜態(tài)代碼塊將驅動注冊到驅動管理器中的registeredDrivers集合變量中的,以MySQL驅動為例:


在驅動管理器的getConnection方法中會遍歷SPI查找到的可用驅動,通過驅動獲取鏈接,直至鏈接獲取成功才返回。

總結
到此這篇關于JAVA中的SPI思想介紹的文章就介紹到這了,更多相關JAVA SPI思想內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Atomikos + MybatisPlus解決多數據源事務一致性問題解決
在實際項目的開發(fā)過程中,我們經常會遇到在同一個項目或微服務中牽涉到使用兩個或多個數據源的,本文主要介紹了Atomikos + MybatisPlus解決多數據源事務一致性問題解決,具有一定的參考價值,感興趣的可以了解一下2024-07-07
Java?spring?MVC環(huán)境中實現WebSocket的示例代碼
這篇文章主要介紹了Java?spring?MVC環(huán)境中實現WebSocket,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09
創(chuàng)建網關項目(Spring Cloud Gateway)過程詳解
這篇文章主要介紹了創(chuàng)建網關項目(Spring Cloud Gateway)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-09-09

