Java SPI 機(jī)制知識(shí)點(diǎn)總結(jié)
前言
不知大家現(xiàn)在有沒有去公司復(fù)工,我已經(jīng)在家辦公將近 3 周了,同時(shí)也在家呆了一個(gè)多月;還好工作并沒有受到任何影響,我個(gè)人一直覺得遠(yuǎn)程工作和 IT 行業(yè)是非常契合的,這段時(shí)間的工作效率甚至比在辦公室還高,同時(shí)由于我們公司的業(yè)務(wù)在海外,所以疫情幾乎沒有造成太多影響。
扯遠(yuǎn)了,這次主要是想和大家分享一下 Java 的 SPI 機(jī)制。
還沒看過的朋友的我先做個(gè)前景提要,當(dāng)時(shí)的需求:
我實(shí)現(xiàn)了一個(gè)類似于的 SpringMVC 但卻很輕量的 http 框架 cicada,其中當(dāng)然也需要一個(gè) IOC 容器,可以存放所有的單例 bean。
這個(gè) IOC 容器的實(shí)現(xiàn)我希望可以有多種方式,甚至可以提供一個(gè)接口供其他人實(shí)現(xiàn);當(dāng)然切換這個(gè) IOC 容器的過程肯定是不能存在硬編碼的,也就是這里所提到的可拔插。當(dāng)我想使用 A 的實(shí)現(xiàn)方式時(shí),我就引入 A 的 jar 包,使用 B 時(shí)就引入 B 的包。
先給大家看看兩次實(shí)現(xiàn)的區(qū)別,先從代碼簡(jiǎn)潔程度來說就是 SPI 更勝一籌。
什么是 SPI
在具體分析之前還是先了解下 SPI 是什么?
首先它其實(shí)是 Service provider interface 的簡(jiǎn)寫,翻譯成中文就是服務(wù)提供發(fā)現(xiàn)接口。
不過這里不要被這個(gè)名詞搞混了,這里的服務(wù)發(fā)現(xiàn)和我們常聽到的微服務(wù)中的服務(wù)發(fā)現(xiàn)并不能劃等號(hào)。
就如同上文提到的對(duì) IOC 容器的多種實(shí)現(xiàn)方式 A、B、C(可以把它們理解為服務(wù)),我需要在運(yùn)行時(shí)知道應(yīng)該使用哪一種具體的實(shí)現(xiàn)。
其實(shí)本質(zhì)上來說這就是一種典型的面向接口編程,這一點(diǎn)在我們剛開始學(xué)習(xí)編程的時(shí)候就被反復(fù)強(qiáng)調(diào)了。
SPI 實(shí)踐
接下來我們來如何來利用 SPI 實(shí)現(xiàn)剛才提到的可拔插 IOC 容器。
既然剛才都提到了 SPI 的本質(zhì)就是面向接口編程,所以自然我們首先需要定義一個(gè)接口:
其中包含了一些 Bean 容器所必須的操作:注冊(cè)、獲取、釋放 bean。
為了讓其他人也能實(shí)現(xiàn)自己的 IOC 容器,所以我們將這個(gè)接口單獨(dú)放到一個(gè) Module 中,可供他人引入實(shí)現(xiàn)。
所以當(dāng)我要實(shí)現(xiàn)一個(gè)單例的 IOC 容器時(shí),我只需要新建一個(gè) Module 然后引入剛才的模塊并實(shí)現(xiàn) CicadaBeanFactory 接口即可。
當(dāng)然其中最重要的則是需要在 resources 目錄下新建一個(gè) META-INF/services/top.crossoverjie.cicada.base.bean.CicadaBeanFactory 文件,文件名必須得是我們之前定義接口的全限定名(SPI 規(guī)范)。
其中的內(nèi)容便是我們自己實(shí)現(xiàn)類的全限定名:
top.crossoverjie.cicada.bean.ioc.CicadaIoc
可以想象最終會(huì)通過這里的全限定名來反射創(chuàng)建對(duì)象。
只不過這個(gè)過程 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)類(可以有多個(gè)實(shí)現(xiàn)類同時(shí)存在,只不過通常我們只需要一個(gè))。
一些都準(zhǔn)備好之后,使用自然就非常簡(jiǎn)單了。
<dependency> <groupId>top.crossoverjie.opensource</groupId> <artifactId>cicada-ioc</artifactId> <version>2.0.4</version> </dependency>
我們只需要引入這個(gè)依賴便能使用它的實(shí)現(xiàn),當(dāng)我們想換一種實(shí)現(xiàn)方式時(shí)只需要更換一個(gè)依賴即可。
這樣就做到了不修改一行代碼靈活的可拔插選擇 IOC 容器了。
SPI 的一些其他應(yīng)用
雖然平時(shí)并不會(huì)直接使用到 SPI 來實(shí)現(xiàn)業(yè)務(wù),但其實(shí)我們使用過的絕大多數(shù)框架都會(huì)提供 SPI 接口方便使用者擴(kuò)展自己的功能。
比如 Dubbo 中提供一系列的擴(kuò)展:
同類型的 RPC 框架 motan 中也提供了響應(yīng)的擴(kuò)展:
他們的使用方式都和 Java SPI 非常類似,只不過原理略有不同,同時(shí)也新增了一些功能。
比如 motan 的 spi 允許是否為單例等等。
再比如 MySQL 的驅(qū)動(dòng)包也是利用 SPI 來實(shí)現(xiàn)自己的連接邏輯。
總結(jié)
Java 自身的 SPI 其實(shí)也有點(diǎn)小毛病,比如:
遍歷加載所有實(shí)現(xiàn)類效率較低。當(dāng)多個(gè) ServiceLoader 同時(shí) load 時(shí)會(huì)有并發(fā)問題(雖然沒人這么干)。
最后總結(jié)一下,SPI 并不是某項(xiàng)高深的技術(shù),本質(zhì)就是面向接口編程,而面向接口本身在我們?nèi)粘i_發(fā)中也是必備技能,所以了解使用 SPI 也是很用處的。
本文所有源碼:
https://github.com/TogetherOS/cicada
到此這篇關(guān)于Java SPI 機(jī)制知識(shí)點(diǎn)總結(jié)的文章就介紹到這了,更多相關(guān)Java SPI 機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java結(jié)構(gòu)性設(shè)計(jì)模式中的裝飾器模式介紹使用
裝飾器模式又名包裝(Wrapper)模式。裝飾器模式以對(duì)客戶端透明的方式拓展對(duì)象的功能,是繼承關(guān)系的一種替代方案,本篇文章以虹貓藍(lán)兔生動(dòng)形象的為你帶來詳細(xì)講解2022-09-09全網(wǎng)最全SpringBoot集成swagger的詳細(xì)教程
swagger是當(dāng)下比較流行的實(shí)時(shí)接口文文檔生成工具,swagger分為swagger2?和swagger3兩個(gè)常用版本,二者區(qū)別不是很大,主要對(duì)于依賴和注解進(jìn)行了優(yōu)化,swagger2需要引入2個(gè)jar包,swagger3只需要一個(gè),用起來沒有什么大的區(qū)別,本文給大家詳細(xì)介紹,感興趣的朋友一起看看吧2022-08-08MyBatis自定義TypeHandler如何解決字段映射問題
這篇文章主要介紹了MyBatis自定義TypeHandler如何解決字段映射問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12深入淺析ArrayList 和 LinkedList的執(zhí)行效率比較
這篇文章主要介紹了ArrayList 和 LinkedList的執(zhí)行效率比較的相關(guān)資料,需要的朋友可以參考下2017-08-08idea中使用Inputstream流導(dǎo)致中文亂碼解決方法
很多朋友遇到一個(gè)措手不及的問題當(dāng)idea中使用Inputstream流導(dǎo)致中文亂碼及Java FileInputStream讀中文亂碼問題,針對(duì)這兩個(gè)問題很多朋友不知道該如何解決,下面小編把解決方案分享給大家供大家參考2021-05-05