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