Java SPI 機(jī)制知識點(diǎn)總結(jié)
前言
不知大家現(xiàn)在有沒有去公司復(fù)工,我已經(jīng)在家辦公將近 3 周了,同時也在家呆了一個多月;還好工作并沒有受到任何影響,我個人一直覺得遠(yuǎn)程工作和 IT 行業(yè)是非常契合的,這段時間的工作效率甚至比在辦公室還高,同時由于我們公司的業(yè)務(wù)在海外,所以疫情幾乎沒有造成太多影響。
扯遠(yuǎn)了,這次主要是想和大家分享一下 Java 的 SPI 機(jī)制。
還沒看過的朋友的我先做個前景提要,當(dāng)時的需求:
我實(shí)現(xiàn)了一個類似于的 SpringMVC 但卻很輕量的 http 框架 cicada,其中當(dāng)然也需要一個 IOC 容器,可以存放所有的單例 bean。
這個 IOC 容器的實(shí)現(xiàn)我希望可以有多種方式,甚至可以提供一個接口供其他人實(shí)現(xiàn);當(dāng)然切換這個 IOC 容器的過程肯定是不能存在硬編碼的,也就是這里所提到的可拔插。當(dāng)我想使用 A 的實(shí)現(xiàn)方式時,我就引入 A 的 jar 包,使用 B 時就引入 B 的包。

先給大家看看兩次實(shí)現(xiàn)的區(qū)別,先從代碼簡潔程度來說就是 SPI 更勝一籌。
什么是 SPI
在具體分析之前還是先了解下 SPI 是什么?
首先它其實(shí)是 Service provider interface 的簡寫,翻譯成中文就是服務(wù)提供發(fā)現(xiàn)接口。
不過這里不要被這個名詞搞混了,這里的服務(wù)發(fā)現(xiàn)和我們常聽到的微服務(wù)中的服務(wù)發(fā)現(xiàn)并不能劃等號。
就如同上文提到的對 IOC 容器的多種實(shí)現(xiàn)方式 A、B、C(可以把它們理解為服務(wù)),我需要在運(yùn)行時知道應(yīng)該使用哪一種具體的實(shí)現(xiàn)。
其實(shí)本質(zhì)上來說這就是一種典型的面向接口編程,這一點(diǎn)在我們剛開始學(xué)習(xí)編程的時候就被反復(fù)強(qiáng)調(diào)了。
SPI 實(shí)踐
接下來我們來如何來利用 SPI 實(shí)現(xiàn)剛才提到的可拔插 IOC 容器。
既然剛才都提到了 SPI 的本質(zhì)就是面向接口編程,所以自然我們首先需要定義一個接口:

其中包含了一些 Bean 容器所必須的操作:注冊、獲取、釋放 bean。
為了讓其他人也能實(shí)現(xiàn)自己的 IOC 容器,所以我們將這個接口單獨(dú)放到一個 Module 中,可供他人引入實(shí)現(xiàn)。

所以當(dāng)我要實(shí)現(xiàn)一個單例的 IOC 容器時,我只需要新建一個 Module 然后引入剛才的模塊并實(shí)現(xiàn) CicadaBeanFactory 接口即可。
當(dāng)然其中最重要的則是需要在 resources 目錄下新建一個 META-INF/services/top.crossoverjie.cicada.base.bean.CicadaBeanFactory 文件,文件名必須得是我們之前定義接口的全限定名(SPI 規(guī)范)。

其中的內(nèi)容便是我們自己實(shí)現(xiàn)類的全限定名:
top.crossoverjie.cicada.bean.ioc.CicadaIoc
可以想象最終會通過這里的全限定名來反射創(chuàng)建對象。
只不過這個過程 Java 已經(jīng)提供 API 屏蔽掉了:
public static CicadaBeanFactory getCicadaBeanFactory() {
ServiceLoader<CicadaBeanFactory> cicadaBeanFactories = ServiceLoader.load(CicadaBeanFactory.class);
if (cicadaBeanFactories.iterator().hasNext()){
return cicadaBeanFactories.iterator().next() ;
}
return new CicadaDefaultBean();
}
當(dāng) classpath 中存在我們剛才的實(shí)現(xiàn)類(引入實(shí)現(xiàn)類的 jar 包),便可以通過 java.util.ServiceLoader 工具類來找到所有的實(shí)現(xiàn)類(可以有多個實(shí)現(xiàn)類同時存在,只不過通常我們只需要一個)。
一些都準(zhǔn)備好之后,使用自然就非常簡單了。
<dependency>
<groupId>top.crossoverjie.opensource</groupId>
<artifactId>cicada-ioc</artifactId>
<version>2.0.4</version>
</dependency>
我們只需要引入這個依賴便能使用它的實(shí)現(xiàn),當(dāng)我們想換一種實(shí)現(xiàn)方式時只需要更換一個依賴即可。
這樣就做到了不修改一行代碼靈活的可拔插選擇 IOC 容器了。
SPI 的一些其他應(yīng)用
雖然平時并不會直接使用到 SPI 來實(shí)現(xiàn)業(yè)務(wù),但其實(shí)我們使用過的絕大多數(shù)框架都會提供 SPI 接口方便使用者擴(kuò)展自己的功能。
比如 Dubbo 中提供一系列的擴(kuò)展: 
同類型的 RPC 框架 motan 中也提供了響應(yīng)的擴(kuò)展: 
他們的使用方式都和 Java SPI 非常類似,只不過原理略有不同,同時也新增了一些功能。
比如 motan 的 spi 允許是否為單例等等。
再比如 MySQL 的驅(qū)動包也是利用 SPI 來實(shí)現(xiàn)自己的連接邏輯。

總結(jié)
Java 自身的 SPI 其實(shí)也有點(diǎn)小毛病,比如:
遍歷加載所有實(shí)現(xiàn)類效率較低。當(dāng)多個 ServiceLoader 同時 load 時會有并發(fā)問題(雖然沒人這么干)。
最后總結(jié)一下,SPI 并不是某項(xiàng)高深的技術(shù),本質(zhì)就是面向接口編程,而面向接口本身在我們?nèi)粘i_發(fā)中也是必備技能,所以了解使用 SPI 也是很用處的。
本文所有源碼:
https://github.com/TogetherOS/cicada
到此這篇關(guān)于Java SPI 機(jī)制知識點(diǎn)總結(jié)的文章就介紹到這了,更多相關(guān)Java SPI 機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot使用Swagger生成多模塊的API文檔
這篇文章將以?Spring?Boot?多模塊項(xiàng)目為例,為大家詳細(xì)介紹一下如何使用?Swagger?生成多模塊的?API?文檔,感興趣的小伙伴可以了解一下2025-02-02
一文搞懂Spring中@Autowired和@Resource的區(qū)別
@Autowired?和?@Resource?都是?Spring/Spring?Boot?項(xiàng)目中,用來進(jìn)行依賴注入的注解。它們都提供了將依賴對象注入到當(dāng)前對象的功能,但二者卻有眾多不同,并且這也是常見的面試題之一,所以我們今天就來盤它2022-08-08
探究springboot中的TomcatMetricsBinder
springboot的TomcatMetricsBinder主要是接收ApplicationStartedEvent然后創(chuàng)建TomcatMetrics執(zhí)行bindTo進(jìn)行注冊,TomcatMetrics主要注冊了globalRequest、servlet、cache、threadPool、session相關(guān)的指標(biāo),本文給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-11-11
Java Web 實(shí)現(xiàn)QQ登錄功能一個帳號同一時間只能一個人登錄
對于一個帳號在同一時間只能一個人登錄,下文給大家介紹的非常詳細(xì),對java web qq 登錄功能感興趣的朋友一起看看吧2016-11-11
Java基本數(shù)據(jù)類型之間的相互轉(zhuǎn)換詳解
這篇文章主要講解Java中基本數(shù)據(jù)類型的轉(zhuǎn)換,數(shù)據(jù)之間相互轉(zhuǎn)換是經(jīng)常會用到的基礎(chǔ)操作,文中講的很清晰,希望能給大家做一個參考。2022-05-05

