一文搞懂Java SPI機(jī)制的原理與使用
Java
程序員在日常工作中經(jīng)常會(huì)聽(tīng)到 SPI
,而且很多框架都使用了 SPI
的技術(shù),那么問(wèn)題來(lái)了,到底什么是 SPI
呢?今天阿粉就帶大家好好了解一下 SPI。
SPI 概念
SPI
全稱(chēng)是 Service Provider Interface
,是一種 JDK
內(nèi)置的動(dòng)態(tài)加載實(shí)現(xiàn)擴(kuò)展點(diǎn)的機(jī)制,通過(guò) SPI
技術(shù)我們可以動(dòng)態(tài)獲取接口的實(shí)現(xiàn)類(lèi),不用自己來(lái)創(chuàng)建。
這里提到了接口和實(shí)現(xiàn)類(lèi),那么 SPI
技術(shù)上具體有哪些技術(shù)細(xì)節(jié)呢?
- 接口:需要有一個(gè)功能接口;
- 實(shí)現(xiàn)類(lèi):接口只是規(guī)范,具體的執(zhí)行需要有實(shí)現(xiàn)類(lèi)才行,所以不可缺少的需要有實(shí)現(xiàn)類(lèi);
- 配置文件:要實(shí)現(xiàn)
SPI
機(jī)制,必須有一個(gè)與接口同名的文件存放于類(lèi)路徑下面的META-INF/services
文件夾中,并且文件中的每一行的內(nèi)容都是一個(gè)實(shí)現(xiàn)類(lèi)的全路徑; - 類(lèi)加載器
ServiceLoader
:JDK
內(nèi)置的一個(gè)類(lèi)加載器,用于加載配置文件中的實(shí)現(xiàn)類(lèi);
舉個(gè)栗子
上面說(shuō)了 SPI
的幾個(gè)概念,接下來(lái)阿粉就通過(guò)一個(gè)栗子來(lái)帶大家感受一下具體的用法。
第一步
創(chuàng)建一個(gè)接口,這里我們創(chuàng)建一個(gè)解壓縮的接口,其中定義了壓縮和解壓的兩個(gè)方法。
package?com.example.demo.spi; /** ?*?<br> ?*?<b>Function:</b><br> ?*?<b>Author:</b>@author?ziyou<br> ?*?<b>Date:</b>2022-10-08 21:31<br> ?*?<b>Desc:</b>無(wú)<br> ?*/ public?interface?Compresser?{ ??byte[]?compress(byte[]?bytes); ??byte[]?decompress(byte[]?bytes); }
第二步
再寫(xiě)兩個(gè)對(duì)應(yīng)的實(shí)現(xiàn)類(lèi),分別是 GzipCompresser.java
和 WinRarCompresser.java
代碼如下
package?com.example.demo.spi.impl; import?com.example.demo.spi.Compresser; import?java.nio.charset.StandardCharsets; /** ?*?<br> ?*?<b>Function:</b><br> ?*?<b>Author:</b>@author?ziyou<br> ?*?<b>Date:</b>2022-10-08 21:33<br> ?*?<b>Desc:</b>無(wú)<br> ?*/ public?class?GzipCompresser?implements?Compresser?{ ??@Override ??public?byte[]?compress(byte[]?bytes)?{ ????return"compress?by?Gzip".getBytes(StandardCharsets.UTF_8); ??} ??@Override ??public?byte[]?decompress(byte[]?bytes)?{ ????return?"decompress?by?Gzip".getBytes(StandardCharsets.UTF_8); ??} }
package?com.example.demo.spi.impl; import?com.example.demo.spi.Compresser; import?java.nio.charset.StandardCharsets; /** ?*? ?*?<b>Function:</b> ?*?<b>Author:</b>@author?ziyou ?*?<b>Date:</b>2022-10-08 21:33 ?*?<b>Desc:</b>無(wú) ?*/ public?class?WinRarCompresser?implements?Compresser?{ ??@Override ??public?byte[]?compress(byte[]?bytes)?{ ????return?"compress?by?WinRar".getBytes(StandardCharsets.UTF_8); ??} ??@Override ??public?byte[]?decompress(byte[]?bytes)?{ ????return?"decompress?by?WinRar".getBytes(StandardCharsets.UTF_8); ??} }
第三步
創(chuàng)建配置文件,我們接著在 resources
目錄下創(chuàng)建一個(gè)名為 META-INF/services
的文件夾,在其中創(chuàng)建一個(gè)名為 com.example.demo.spi.Compresser
的文件,其中的內(nèi)容如下:
com.example.demo.spi.impl.WinRarCompresser com.example.demo.spi.impl.GzipCompresser
注意該文件的名稱(chēng)必須是接口的全路徑,文件里面的內(nèi)容每一行都是一個(gè)實(shí)現(xiàn)類(lèi)的全路徑,多個(gè)實(shí)現(xiàn)類(lèi)就寫(xiě)在多行里面,效果如下。
第四步
有了上面的接口,實(shí)現(xiàn)類(lèi)和配置文件,接下來(lái)我們就可以使用 ServiceLoader
動(dòng)態(tài)加載實(shí)現(xiàn)類(lèi),來(lái)實(shí)現(xiàn) SPI
技術(shù)了,如下所示:
package?com.example.demo; import?com.example.demo.spi.Compresser; import?java.nio.charset.StandardCharsets; import?java.util.ServiceLoader; public?class?TestSPI?{ ??public?static?void?main(String[]?args)?{ ????ServiceLoader<Compresser>?compressers?=?ServiceLoader.load(Compresser.class); ????for?(Compresser?compresser?:?compressers)?{ ??????System.out.println(compresser.getClass()); ????} ??} }
運(yùn)行的結(jié)果如下
可以看到我們正常的獲取到了接口的實(shí)現(xiàn)類(lèi),并且可以直接使用實(shí)現(xiàn)類(lèi)的解壓縮方法。
原理
知道了如何使用 SPI
接下來(lái)我們來(lái)研究一下是如何實(shí)現(xiàn)的,通過(guò)上面的測(cè)試我們可以看到,核心的邏輯是 ServiceLoader.load()
方法,這個(gè)方法有點(diǎn)類(lèi)似于 Spring
中的根據(jù)接口獲取所有實(shí)現(xiàn)類(lèi)一樣。
點(diǎn)開(kāi) ServiceLoader
我們可以看到有一個(gè)常量 PREFIX
,如下所示,這也是為什么我們必須在這個(gè)路徑下面創(chuàng)建配置文件,因?yàn)?code> JDK 代碼里面會(huì)從這個(gè)路徑里面去讀取我們的文件。
同時(shí)又因?yàn)樵谧x取文件的時(shí)候使用了 class
的路徑名稱(chēng),因?yàn)槲覀兪褂?nbsp;load
方法的時(shí)候只會(huì)傳遞一個(gè) class
,所以我們的文件名也必須是接口的全路徑。
通過(guò) load
方法我們可以看到底層構(gòu)造了一個(gè) java.util.ServiceLoader.LazyIterator
迭代器。
在迭代器中的 parse
方法中,就獲取了配置文件中的實(shí)現(xiàn)類(lèi)名稱(chēng)集合,然后在通過(guò)反射創(chuàng)建出具體的實(shí)現(xiàn)類(lèi)對(duì)象存放到 LinkedHashMap<String,S> providers = new LinkedHashMap<>();
中。
常用的框架
SPI 技術(shù)的使用非常廣泛,比如在 Dubble
,不過(guò) Dubble
中的 SPI
有經(jīng)過(guò)改造的,還有我們很常見(jiàn)的數(shù)據(jù)庫(kù)的驅(qū)動(dòng)中也使用了 SPI
,感興趣的小伙伴可以去翻翻看,還有 SLF4J
用來(lái)加載不同提供商的日志實(shí)現(xiàn)類(lèi)以及 Spring
框架等。
優(yōu)缺點(diǎn)
前面介紹了 SPI
的原理和使用,那 SPI
有什么優(yōu)缺點(diǎn)呢?
優(yōu)點(diǎn)
優(yōu)點(diǎn)當(dāng)然是解耦,服務(wù)方只要定義好接口規(guī)范就好了,具體的實(shí)現(xiàn)可以由不同的 Jar
進(jìn)行實(shí)現(xiàn),只要按照規(guī)范實(shí)現(xiàn)功能就可以被直接拿來(lái)使用,在某些場(chǎng)合會(huì)被進(jìn)行熱插拔使用,實(shí)現(xiàn)了解耦的功能。
缺點(diǎn)
一個(gè)很明顯的缺點(diǎn)那就是做不到按需加載,通過(guò)源碼我們看到了是會(huì)將所有的實(shí)現(xiàn)類(lèi)都進(jìn)行創(chuàng)建的,這種做法會(huì)降低性能,如果某些實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)很耗時(shí)了話(huà)將影響加載時(shí)間。同時(shí)實(shí)現(xiàn)類(lèi)的命名也沒(méi)有規(guī)范,讓使用者不方便引用。
到此這篇關(guān)于一文搞懂Java SPI機(jī)制的原理與使用的文章就介紹到這了,更多相關(guān)Java SPI機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot(五)之跨域、自定義查詢(xún)及分頁(yè)
這篇文章主要介紹了Spring Boot(五)之跨域、自定義查詢(xún)及分頁(yè)的的相關(guān)資料,需要的朋友可以參考下2017-04-04Flink自定義Sink端實(shí)現(xiàn)過(guò)程講解
這篇文章主要介紹了Flink自定義Sink端實(shí)現(xiàn)過(guò)程,在Fink官網(wǎng)中sink端只是給出了常規(guī)的write api.在我們實(shí)際開(kāi)發(fā)場(chǎng)景中需要將flink處理的數(shù)據(jù)寫(xiě)入kafka,hbase kudu等外部系統(tǒng)2023-01-01Debian 7 和 Debian 8 用戶(hù)安裝 Java 8的方法
Oracle Java 8 穩(wěn)定版本近期已發(fā)布,有很多新的特征變化。其中,有功能的程序支持通過(guò)“Lambda項(xiàng)目 ”,收到了一些安全更新和界面改進(jìn)上的bug修復(fù),使得開(kāi)發(fā)人員的工作更容易。2014-03-03JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)原理解析
這篇文章主要介紹了JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08mybatis打印SQL,并顯示參數(shù)的實(shí)例
這篇文章主要介紹了mybatis打印SQL,并顯示參數(shù)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12JAVA中excel導(dǎo)出一對(duì)多合并具體實(shí)現(xiàn)
項(xiàng)目中經(jīng)常會(huì)使用到導(dǎo)出功能,有導(dǎo)出Word,有導(dǎo)出Excel的,下面這篇文章主要給大家介紹了關(guān)于JAVA中excel導(dǎo)出一對(duì)多合并具體實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2023-09-09java 使用線(xiàn)程做的一個(gè)簡(jiǎn)單的ATM存取款實(shí)例代碼
線(xiàn)程 Thread 類(lèi),和 Runable 接口 比較兩者的特點(diǎn)和應(yīng)用領(lǐng)域.可以,直接繼承線(xiàn)程Thread類(lèi)。該方法編寫(xiě)簡(jiǎn)單,可以直接操作線(xiàn)程,適用于單重繼承情況,因而不能在繼承其他類(lèi),下面我們來(lái)看一個(gè)實(shí)例2013-08-08