1. 程式人生 > 程式設計 >Java SPI 機制知識點總結

Java SPI 機制知識點總結

前言

不知大家現在有沒有去公司復工,我已經在家辦公將近 3 周了,同時也在家呆了一個多月;還好工作並沒有受到任何影響,我個人一直覺得遠端工作和 IT 行業是非常契合的,這段時間的工作效率甚至比在辦公室還高,同時由於我們公司的業務在海外,所以疫情幾乎沒有造成太多影響。

扯遠了,這次主要是想和大家分享一下 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 機制知識點總結

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

top.crossoverjie.cicada.bean.ioc.CicadaIoc

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

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

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

    return new CicadaDefaultBean();
  }

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

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

<dependency>
    <groupId>top.crossoverjie.opensource</groupId>
    <artifactId>cicada-ioc</artifactId>
    <version>2.0.4</version>
  </dependency>

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

這樣就做到了不修改一行程式碼靈活的可拔插選擇 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

到此這篇關於Java SPI 機制知識點總結的文章就介紹到這了,更多相關Java SPI 機制內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!