一文搞懂Java SPI機制的原理與使用
Java
程序員在日常工作中經(jīng)常會聽到 SPI
,而且很多框架都使用了 SPI
的技術(shù),那么問題來了,到底什么是 SPI
呢?今天阿粉就帶大家好好了解一下 SPI。
SPI 概念
SPI
全稱是 Service Provider Interface
,是一種 JDK
內(nèi)置的動態(tài)加載實現(xiàn)擴展點的機制,通過 SPI
技術(shù)我們可以動態(tài)獲取接口的實現(xiàn)類,不用自己來創(chuàng)建。
這里提到了接口和實現(xiàn)類,那么 SPI
技術(shù)上具體有哪些技術(shù)細(xì)節(jié)呢?
- 接口:需要有一個功能接口;
- 實現(xiàn)類:接口只是規(guī)范,具體的執(zhí)行需要有實現(xiàn)類才行,所以不可缺少的需要有實現(xiàn)類;
- 配置文件:要實現(xiàn)
SPI
機制,必須有一個與接口同名的文件存放于類路徑下面的META-INF/services
文件夾中,并且文件中的每一行的內(nèi)容都是一個實現(xiàn)類的全路徑; - 類加載器
ServiceLoader
:JDK
內(nèi)置的一個類加載器,用于加載配置文件中的實現(xiàn)類;
舉個栗子
上面說了 SPI
的幾個概念,接下來阿粉就通過一個栗子來帶大家感受一下具體的用法。
第一步
創(chuàng)建一個接口,這里我們創(chuàng)建一個解壓縮的接口,其中定義了壓縮和解壓的兩個方法。
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>無<br> ?*/ public?interface?Compresser?{ ??byte[]?compress(byte[]?bytes); ??byte[]?decompress(byte[]?bytes); }
第二步
再寫兩個對應(yīng)的實現(xiàn)類,分別是 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>無<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>無 ?*/ 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)建一個名為 META-INF/services
的文件夾,在其中創(chuàng)建一個名為 com.example.demo.spi.Compresser
的文件,其中的內(nèi)容如下:
com.example.demo.spi.impl.WinRarCompresser com.example.demo.spi.impl.GzipCompresser
注意該文件的名稱必須是接口的全路徑,文件里面的內(nèi)容每一行都是一個實現(xiàn)類的全路徑,多個實現(xiàn)類就寫在多行里面,效果如下。
第四步
有了上面的接口,實現(xiàn)類和配置文件,接下來我們就可以使用 ServiceLoader
動態(tài)加載實現(xiàn)類,來實現(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()); ????} ??} }
運行的結(jié)果如下
可以看到我們正常的獲取到了接口的實現(xiàn)類,并且可以直接使用實現(xiàn)類的解壓縮方法。
原理
知道了如何使用 SPI
接下來我們來研究一下是如何實現(xiàn)的,通過上面的測試我們可以看到,核心的邏輯是 ServiceLoader.load()
方法,這個方法有點類似于 Spring
中的根據(jù)接口獲取所有實現(xiàn)類一樣。
點開 ServiceLoader
我們可以看到有一個常量 PREFIX
,如下所示,這也是為什么我們必須在這個路徑下面創(chuàng)建配置文件,因為 JDK
代碼里面會從這個路徑里面去讀取我們的文件。
同時又因為在讀取文件的時候使用了 class
的路徑名稱,因為我們使用 load
方法的時候只會傳遞一個 class
,所以我們的文件名也必須是接口的全路徑。
通過 load
方法我們可以看到底層構(gòu)造了一個 java.util.ServiceLoader.LazyIterator
迭代器。
在迭代器中的 parse
方法中,就獲取了配置文件中的實現(xiàn)類名稱集合,然后在通過反射創(chuàng)建出具體的實現(xiàn)類對象存放到 LinkedHashMap<String,S> providers = new LinkedHashMap<>();
中。
常用的框架
SPI 技術(shù)的使用非常廣泛,比如在 Dubble
,不過 Dubble
中的 SPI
有經(jīng)過改造的,還有我們很常見的數(shù)據(jù)庫的驅(qū)動中也使用了 SPI
,感興趣的小伙伴可以去翻翻看,還有 SLF4J
用來加載不同提供商的日志實現(xiàn)類以及 Spring
框架等。
優(yōu)缺點
前面介紹了 SPI
的原理和使用,那 SPI
有什么優(yōu)缺點呢?
優(yōu)點
優(yōu)點當(dāng)然是解耦,服務(wù)方只要定義好接口規(guī)范就好了,具體的實現(xiàn)可以由不同的 Jar
進行實現(xiàn),只要按照規(guī)范實現(xiàn)功能就可以被直接拿來使用,在某些場合會被進行熱插拔使用,實現(xiàn)了解耦的功能。
缺點
一個很明顯的缺點那就是做不到按需加載,通過源碼我們看到了是會將所有的實現(xiàn)類都進行創(chuàng)建的,這種做法會降低性能,如果某些實現(xiàn)類實現(xiàn)很耗時了話將影響加載時間。同時實現(xiàn)類的命名也沒有規(guī)范,讓使用者不方便引用。
到此這篇關(guān)于一文搞懂Java SPI機制的原理與使用的文章就介紹到這了,更多相關(guān)Java SPI機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Debian 7 和 Debian 8 用戶安裝 Java 8的方法
Oracle Java 8 穩(wěn)定版本近期已發(fā)布,有很多新的特征變化。其中,有功能的程序支持通過“Lambda項目 ”,收到了一些安全更新和界面改進上的bug修復(fù),使得開發(fā)人員的工作更容易。2014-03-03JAVA中excel導(dǎo)出一對多合并具體實現(xiàn)
項目中經(jīng)常會使用到導(dǎo)出功能,有導(dǎo)出Word,有導(dǎo)出Excel的,下面這篇文章主要給大家介紹了關(guān)于JAVA中excel導(dǎo)出一對多合并具體實現(xiàn)的相關(guān)資料,需要的朋友可以參考下2023-09-09