一文搞懂Java的SPI機(jī)制(推薦)
1 簡介
SPI,Service Provider Interface,一種服務(wù)發(fā)現(xiàn)機(jī)制。
有了SPI,即可實(shí)現(xiàn)服務(wù)接口與服務(wù)實(shí)現(xiàn)的解耦:
- 服務(wù)提供者(如 springboot starter)提供出 SPI 接口。身為服務(wù)提供者,在你無法形成絕對規(guī)范強(qiáng)制時(shí),適度"放權(quán)" 比較明智,適當(dāng)讓客戶端去自定義實(shí)現(xiàn)
- 客戶端(普通的 springboot 項(xiàng)目)即可通過本地注冊的形式,將實(shí)現(xiàn)類注冊到服務(wù)端,輕松實(shí)現(xiàn)可插拔
缺點(diǎn)
- 不能按需加載。
- 雖然 ServiceLoader 做了延遲加載,但是只能通過遍歷的方式全部獲取。如果其中某些實(shí)現(xiàn)類很耗時(shí),而且你也不需要加載它,那么就形成了資源浪費(fèi)獲取某個實(shí)現(xiàn)類的方式不夠靈活,只能通過迭代器的形式獲取
Dubbo SPI 實(shí)現(xiàn)方式對以上兩點(diǎn)進(jìn)行了業(yè)務(wù)優(yōu)化。
源碼
應(yīng)用程序通過迭代器接口獲取對象實(shí)例,這里首先會判斷 providers 對象中是否有實(shí)例對象:
- 有實(shí)例,那么就返回
- 沒有,執(zhí)行類的裝載步驟,具體類裝載實(shí)現(xiàn)如下:
LazyIterator#hasNextService 讀取 META-INF/services 下的配置文件,獲得所有能被實(shí)例化的類的名稱,并完成 SPI 配置文件的解析
LazyIterator#nextService 負(fù)責(zé)實(shí)例化 hasNextService() 讀到的實(shí)現(xiàn)類,并將實(shí)例化后的對象存放到 providers 集合中緩存
使用
如某接口有3個實(shí)現(xiàn)類,那系統(tǒng)運(yùn)行時(shí),該接口到底選擇哪個實(shí)現(xiàn)類呢?
這時(shí)就需要SPI,根據(jù)指定或默認(rèn)配置,找到對應(yīng)實(shí)現(xiàn)類,加載進(jìn)來,然后使用該實(shí)現(xiàn)類實(shí)例。
如下系統(tǒng)運(yùn)行時(shí),加載配置,用實(shí)現(xiàn)A2實(shí)例化一個對象來提供服務(wù):
再如,你要通過jar包給某個接口提供實(shí)現(xiàn),就在自己jar包的META-INF/services/
目錄下放一個接口同名文件,指定接口的實(shí)現(xiàn)是自己這個jar包里的某類即可:
別人用這個接口,然后用你的jar包,就會在運(yùn)行時(shí)通過你的jar包指定文件找到這個接口該用哪個實(shí)現(xiàn)類。這是JDK內(nèi)置提供的功能。
我就不定義在 META-INF/services 下面行不行?就想定義在別的地方可以嗎?
No!JDK 已經(jīng)規(guī)定好配置路徑,你若隨便定義,類加載器可就不知道去哪里加載了
假設(shè)你有個工程P,有個接口A,A在P無實(shí)現(xiàn)類,系統(tǒng)運(yùn)行時(shí)怎么給A選實(shí)現(xiàn)類呢?
可以自己搞個jar包,META-INF/services/
,放上一個文件,文件名即接口名,接口A的實(shí)現(xiàn)類=com.javaedge.service.實(shí)現(xiàn)類A2
。
讓P來依賴你的jar包,等系統(tǒng)運(yùn)行時(shí),P跑起來了,對于接口A,就會掃描依賴的jar包,看看有沒有META-INF/services
文件夾:
有,再看看有無名為接口A的文件: 有,在里面查找指定的接口A的實(shí)現(xiàn)是你的jar包里的哪個類即可
適用場景
插件擴(kuò)展
比如你開發(fā)了一個開源框架,若你想讓別人自己寫個插件,安排到你的開源框架里中,擴(kuò)展功能時(shí)。
如JDBC。Java定義了一套JDBC的接口,但并未提供具體實(shí)現(xiàn)類,而是在不同云廠商提供的數(shù)據(jù)庫實(shí)現(xiàn)包。
但項(xiàng)目運(yùn)行時(shí),要使用JDBC接口的哪些實(shí)現(xiàn)類呢?
一般要根據(jù)自己使用的數(shù)據(jù)庫驅(qū)動jar包,比如我們最常用的MySQL,其mysql-jdbc-connector.jar
里面就有:
系統(tǒng)運(yùn)行時(shí)碰到你使用JDBC的接口,就會在底層使用你引入的那個jar中提供的實(shí)現(xiàn)類。
案例
如sharding-jdbc 數(shù)據(jù)加密模塊,本身支持 AES 和 MD5 兩種加密方式。但若客戶端不想用內(nèi)置的兩種加密,偏偏想用 RSA 算法呢?難道每加一種算法,sharding-jdbc 就要發(fā)個版本?
sharding-jdbc 可不會這么蠢,首先提供出 EncryptAlgorithm 加密算法接口,并引入 SPI 機(jī)制,做到服務(wù)接口與服務(wù)實(shí)現(xiàn)分離的效果。
客戶端想要使用自定義加密算法,只需在客戶端項(xiàng)目 META-INF/services
的路徑下定義接口的全限定名稱文件,并在文件內(nèi)寫上加密實(shí)現(xiàn)類的全限定名
這就顯示了SPI的優(yōu)點(diǎn):
- 客戶端(自己的項(xiàng)目)提供了服務(wù)端(sharding-jdbc)的接口自定義實(shí)現(xiàn),但是與服務(wù)端狀態(tài)分離,只有在客戶端提供了自定義接口實(shí)現(xiàn)時(shí)才會加載,其它并沒有關(guān)聯(lián);客戶端的新增或刪除實(shí)現(xiàn)類不會影響服務(wù)端
- 如果客戶端不想要 RSA 算法,又想要使用內(nèi)置的 AES 算法,那么可以隨時(shí)刪掉實(shí)現(xiàn)類,可擴(kuò)展性強(qiáng),插件化架構(gòu)
到此這篇關(guān)于一文搞懂Java的SPI機(jī)制的文章就介紹到這了,更多相關(guān)Java的SPI機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
RabbitMQ的消息確認(rèn)機(jī)制的詳細(xì)總結(jié)
RabbitMQ消息確認(rèn)機(jī)制指的是在消息傳遞過程中,發(fā)送方發(fā)送消息后,接收方需要對消息進(jìn)行確認(rèn),以確保消息被正確地接收和處理,本文就講給大家詳解介紹RabbitMQ的幾種消息確認(rèn)機(jī)制,需要的朋友可以參考下2023-07-07將java中的 string 類型轉(zhuǎn)成 數(shù)組案例
這篇文章主要介紹了將java中的 string 類型轉(zhuǎn)成 數(shù)組案例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09引入SpringCloud-gateway報(bào)錯的解決方案
這篇文章主要介紹了引入SpringCloud-gateway報(bào)錯的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Java實(shí)現(xiàn)TCP和UDP協(xié)議詳解
這篇文章主要介紹了Java實(shí)現(xiàn)TCP和UDP協(xié)議詳解,TCP(傳輸控制協(xié)議)和UDP(用戶數(shù)據(jù)報(bào)協(xié)議)是兩種最常用的傳輸層協(xié)議,它們都用于在網(wǎng)絡(luò)上傳輸數(shù)據(jù),但是它們之間有很多不同之處,需要的朋友可以參考下2023-07-07Spring Cloud 系列之服務(wù)調(diào)用 OpenFeign的實(shí)現(xiàn)
這篇文章主要介紹了Spring Cloud 系列之服務(wù)調(diào)用 OpenFeign的實(shí)現(xiàn),需要的朋友可以參考下2020-11-11Java中Object.equals和String.equals的區(qū)別詳解
這篇文章主要給大家介紹了Java中Object.equals和String.equals的區(qū)別,文中通過一個小示例讓大家輕松的明白這兩者的區(qū)別,對大家具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。2017-04-04Log4j 配置日志打印時(shí)區(qū)的實(shí)現(xiàn)方法
下面小編就為大家分享一篇Log4j 配置日志打印時(shí)區(qū)的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12