一文搞懂Java的SPI機制(推薦)

1 簡介

SPI,Service Provider Interface,一種服務發現機制。


有瞭SPI,即可實現服務接口與服務實現的解耦:

  • 服務提供者(如 springboot starter)提供出 SPI 接口。身為服務提供者,在你無法形成絕對規范強制時,適度”放權” 比較明智,適當讓客戶端去自定義實現
  • 客戶端(普通的 springboot 項目)即可通過本地註冊的形式,將實現類註冊到服務端,輕松實現可插拔

缺點

  • 不能按需加載。
  • 雖然 ServiceLoader 做瞭延遲加載,但是隻能通過遍歷的方式全部獲取。如果其中某些實現類很耗時,而且你也不需要加載它,那麼就形成瞭資源浪費獲取某個實現類的方式不夠靈活,隻能通過迭代器的形式獲取

Dubbo SPI 實現方式對以上兩點進行瞭業務優化。

源碼

應用程序通過迭代器接口獲取對象實例,這裡首先會判斷 providers 對象中是否有實例對象:

  • 有實例,那麼就返回
  • 沒有,執行類的裝載步驟,具體類裝載實現如下:

LazyIterator#hasNextService 讀取 META-INF/services 下的配置文件,獲得所有能被實例化的類的名稱,並完成 SPI 配置文件的解析

LazyIterator#nextService 負責實例化 hasNextService() 讀到的實現類,並將實例化後的對象存放到 providers 集合中緩存

使用

如某接口有3個實現類,那系統運行時,該接口到底選擇哪個實現類呢?
這時就需要SPI,根據指定或默認配置,找到對應實現類,加載進來,然後使用該實現類實例

如下系統運行時,加載配置,用實現A2實例化一個對象來提供服務:

再如,你要通過jar包給某個接口提供實現,就在自己jar包的META-INF/services/目錄下放一個接口同名文件,指定接口的實現是自己這個jar包裡的某類即可:

別人用這個接口,然後用你的jar包,就會在運行時通過你的jar包指定文件找到這個接口該用哪個實現類。這是JDK內置提供的功能。

我就不定義在 META-INF/services 下面行不行?就想定義在別的地方可以嗎?

No!JDK 已經規定好配置路徑,你若隨便定義,類加載器可就不知道去哪裡加載瞭

假設你有個工程P,有個接口A,A在P無實現類,系統運行時怎麼給A選實現類呢?
可以自己搞個jar包,META-INF/services/,放上一個文件,文件名即接口名,接口A的實現類=com.javaedge.service.實現類A2
讓P來依賴你的jar包,等系統運行時,P跑起來瞭,對於接口A,就會掃描依賴的jar包,看看有沒有META-INF/services文件夾:

有,再看看有無名為接口A的文件: 有,在裡面查找指定的接口A的實現是你的jar包裡的哪個類即可

適用場景

插件擴展

比如你開發瞭一個開源框架,若你想讓別人自己寫個插件,安排到你的開源框架裡中,擴展功能時。

如JDBC。Java定義瞭一套JDBC的接口,但並未提供具體實現類,而是在不同雲廠商提供的數據庫實現包。

但項目運行時,要使用JDBC接口的哪些實現類呢?

一般要根據自己使用的數據庫驅動jar包,比如我們最常用的MySQL,其mysql-jdbc-connector.jar 裡面就有:

系統運行時碰到你使用JDBC的接口,就會在底層使用你引入的那個jar中提供的實現類。

案例

如sharding-jdbc 數據加密模塊,本身支持 AES 和 MD5 兩種加密方式。但若客戶端不想用內置的兩種加密,偏偏想用 RSA 算法呢?難道每加一種算法,sharding-jdbc 就要發個版本?

sharding-jdbc 可不會這麼蠢,首先提供出 EncryptAlgorithm 加密算法接口,並引入 SPI 機制,做到服務接口與服務實現分離的效果。
客戶端想要使用自定義加密算法,隻需在客戶端項目 META-INF/services 的路徑下定義接口的全限定名稱文件,並在文件內寫上加密實現類的全限定名


這就顯示瞭SPI的優點:

  • 客戶端(自己的項目)提供瞭服務端(sharding-jdbc)的接口自定義實現,但是與服務端狀態分離,隻有在客戶端提供瞭自定義接口實現時才會加載,其它並沒有關聯;客戶端的新增或刪除實現類不會影響服務端
  • 如果客戶端不想要 RSA 算法,又想要使用內置的 AES 算法,那麼可以隨時刪掉實現類,可擴展性強,插件化架構

到此這篇關於一文搞懂Java的SPI機制的文章就介紹到這瞭,更多相關Java的SPI機制內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: