你應該瞭解的 Java SPI 機制

你應該瞭解的 Java SPI 機制

前言

這次主要是想和大家分享一下 Java 的 SPI 機制。

還沒看過的朋友的我先做個前景提要,當時的需求:

我實現了一個類似於的 SpringMVC 但卻很輕量的 http 框架 cicada ,其中當然也需要一個 IOC 容器,可以存放所有的單例 bean。

這個 IOC 容器的實現我希望可以有多種方式,甚至可以提供一個接口供其他人實現;當然切換這個 IOC 容器的過程肯定是不能存在硬編碼的,也就是這裡所提到的 可拔插

當我想使用 A 的實現方式時,我就引入 A 的 jar 包,使用 B 時就引入 B 的包。

你應該瞭解的 Java SPI 機制

先給大家看看兩次實現的區別,先從代碼簡潔程度來說就是 SPI 更勝一籌。

什麼是 SPI

在具體分析之前還是先了解下 SPI 是什麼?

首先它其實是 Service provider interface 的簡寫,翻譯成中文就是服務提供發現接口。

不過這裡不要被這個名詞搞混了,這裡的 服務發現 和我們常聽到的微服務中的服務發現並不能劃等號。

就如同上文提到的對 IOC 容器的多種實現方式 A、B、C(可以把它們理解為服務),我需要在運行時知道應該使用哪一種具體的實現。

其實本質上來說這就是一種典型的面向接口編程,這一點在我們剛開始學習編程的時候就被反覆強調了。

SPI 實踐

接下來我們來如何來利用 SPI 實現剛才提到的可拔插 IOC 容器。

既然剛才都提到了 SPI 的本質就是面向接口編程,所以自然我們首先需要定義一個接口:

你應該瞭解的 Java SPI 機制

其中包含了一些 Bean 容器所必須的操作:註冊、獲取、釋放 bean。

為了讓其他人也能實現自己的 IOC 容器,所以我們將這個接口單獨放到一個 Module 中,可供他人引入實現。

你應該瞭解的 Java SPI 機制

所以當我要實現一個單例的 IOC 容器時,我只需要新建一個 Module 然後引入剛才的模塊並實現 CicadaBeanFactory 接口即可。

當然其中最重要的則是需要在 resources 目錄下新建一個 META-INF/services/top.crossoverjie.cicada.base.bean.CicadaBeanFactory 文件,文件名必須得是我們之前定義接口的全限定名(SPI 規範)。

你應該瞭解的 Java SPI 機制

其中的內容便是我們自己實現類的全限定名:

<code>top.crossoverjie.cicada.bean.ioc.CicadaIoc/<code>

可以想象最終會通過這裡的全限定名來反射創建對象。

只不過這個過程 Java 已經提供 API 屏蔽掉了:

<code>public static CicadaBeanFactory getCicadaBeanFactory() {
ServiceLoader<cicadabeanfactory> cicadaBeanFactories = ServiceLoader.load(CicadaBeanFactory.class);
if (cicadaBeanFactories.iterator().hasNext()){
return cicadaBeanFactories.iterator().next() ;
}

return new CicadaDefaultBean();
}/<cicadabeanfactory>/<code>

當 classpath 中存在我們剛才的實現類(引入實現類的 jar 包),便可以通過 java.util.ServiceLoader 工具類來找到所有的實現類(可以有多個實現類同時存在,只不過通常我們只需要一個)。

一些都準備好之後,使用自然就非常簡單了。

<code><dependency>
<groupid>top.crossoverjie.opensource/<groupid>
<artifactid>cicada-ioc/<artifactid>
<version>2.0.4/<version>
/<dependency>/<code>

我們只需要引入這個依賴便能使用它的實現,當我們想換一種實現方式時只需要更換一個依賴即可。

這樣就做到了不修改一行代碼靈活的 可拔插 選擇 IOC 容器了。

SPI 的一些其他應用

雖然平時並不會直接使用到 SPI 來實現業務,但其實我們使用過的絕大多數框架都會提供 SPI 接口方便使用者擴展自己的功能。

比如 Dubbo 中提供一系列的擴展:

你應該瞭解的 Java SPI 機制

同類型的 RPC 框架 motan 中也提供了響應的擴展:

你應該瞭解的 Java SPI 機制

他們的使用方式都和 Java SPI 非常類似,只不過原理略有不同,同時也新增了一些功能。

比如 motan 的 spi 允許是否為單例等等。

再比如 MySQL 的驅動包也是利用 SPI 來實現自己的連接邏輯。

你應該瞭解的 Java SPI 機制

總結

Java 自身的 SPI 其實也有點小毛病,比如:

  • 遍歷加載所有實現類效率較低。
  • 當多個 ServiceLoader 同時 load 時會有併發問題(雖然沒人這麼幹)。

最後總結一下, SPI 並不是某項高深的技術,本質就是面向接口編程,而面向接口本身在我們日常開發中也是必備技能,所以瞭解使用 SPI 也是很用處的。

本文所有源碼:

https://github.com/TogetherOS/cicada


分享到:


相關文章: