不知你們如今有沒有去公司復工,我已經在家辦公將近 3 周了,同時也在家呆了一個多月;還好工做並無受到任何影響,我我的一直以爲遠程工做和 IT 行業是很是契合的,這段時間的工做效率甚至比在辦公室還高,同時因爲咱們公司的業務在海外,因此疫情幾乎沒有形成太多影響。java
扯遠了,此次主要是想和你們分享一下 Java
的 SPI
機制。週末沒啥事,我翻了翻我以前的寫的博客 《設計一個可拔插的 IOC 容器》,發現當時的實現並不那麼優雅。git
還沒看過的朋友的我先作個前景提要,當時的需求:github
我實現了一個相似於的 SpringMVC 但卻很輕量的 http 框架 cicada,其中固然也須要一個 IOC 容器,能夠存放全部的單例 bean。編程
這個 IOC 容器的實現我但願能夠有多種方式,甚至能夠提供一個接口供其餘人實現;固然切換這個 IOC 容器的過程確定是不能存在硬編碼的,也就是這裏所提到的可拔插。 當我想使用 A 的實現方式時,我就引入 A 的 jar 包,使用 B 時就引入 B 的包。併發
先給你們看看兩次實現的區別,先從代碼簡潔程度來講就是 SPI
更勝一籌。框架
在具體分析以前仍是先了解下 SPI
是什麼?ide
首先它實際上是 Service provider interface
的簡寫,翻譯成中文就是服務提供發現接口。微服務
不過這裏不要被這個名詞搞混了,這裏的服務發現
和咱們常聽到的微服務中的服務發現並不能劃等號。工具
就如同上文提到的對 IOC
容器的多種實現方式 A、B、C(能夠把它們理解爲服務),我須要在運行時知道應該使用哪種具體的實現。學習
其實本質上來講這就是一種典型的面向接口編程,這一點在咱們剛開始學習編程的時候就被反覆強調了。
接下來咱們來如何來利用 SPI 實現剛纔提到的可拔插 IOC 容器。
既然剛纔都提到了 SPI 的本質就是面向接口編程,因此天然咱們首先須要定義一個接口:
其中包含了一些 Bean
容器所必須的操做:註冊、獲取、釋放 bean。
爲了讓其餘人也能實現本身的 IOC
容器,因此咱們將這個接口單獨放到一個 Module
中,可供他人引入實現。
因此當我要實現一個單例的 IOC
容器時,我只須要新建一個 Module
而後引入剛纔的模塊並實現 CicadaBeanFactory
接口便可。
固然其中最重要的則是須要在 resources
目錄下新建一個 META-INF/services/top.crossoverjie.cicada.base.bean.CicadaBeanFactory
文件,文件名必須得是咱們以前定義接口的全限定名(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 接口方便使用者擴展本身的功能。
好比 Dubbo
中提供一系列的擴展:
同類型的 RPC
框架 motan
中也提供了響應的擴展:
他們的使用方式都和 Java SPI 很是相似,只不過原理略有不一樣,同時也新增了一些功能。
好比 motan
的 spi
容許是否爲單例等等。
再好比 MySQL 的驅動包也是利用 SPI 來實現本身的鏈接邏輯。
Java
自身的 SPI
其實也有點小毛病,好比:
ServiceLoader
同時 load
時會有併發問題(雖然沒人這麼幹)。最後總結一下,SPI
並非某項高深的技術,本質就是面向接口編程,而面向接口自己在咱們平常開發中也是必備技能,因此瞭解使用 SPI
也是很用處的。
本文全部源碼:
你的點贊與分享是對我最大的支持