【一文讀懂】SPI機制之JAVA的SPI實現詳解

程序员木木熊發表於2024-12-05

🐻大家好,我是木木熊
🌍️公眾號:「程式設計師木木熊 」
本文以學習交流和分享為目的,如有不正確的地方,歡迎大家批評指正!!

文章較長,但內容超值,可以先收藏、關注~~

什麼是SPI

SPI全稱Service Provider Interface,翻譯過來是“服務提供者的介面”,單從這個翻譯,確實讓人摸不著頭腦。

我們來看看一些常見的關於SPI說法

  • SPI是一種在Java中用於實現模組化和可插拔性的機制。
  • SPI是JDK內建的一種服務發現機制,可以用來啟用框架擴充套件和替換元件。
  • SPI是JDK內建的一種服務發現機制,用於制定一些規範,實際實現方式交給不同的服務廠商
  • SPI機制保證多個實現可以動態地載入和使用,在不修改程式碼的情況下,替換或新增新的服務實現,提升系統靈活度。

不難發現,SPI的這些說法中,無一例外的提到了,SPI是一種機制,是一種擴充機制。

這種機制,可以實現動態的新增和替換服務實現,常用在框架和元件的開發中,實現了模組化和可插拔性。

SPI簡單使用示例

我們先看個簡單的例子,然後再深入JDK的原始碼看具體是怎麼實現的。

整體的程式碼結構

  • 一個日誌工具類,LogUtil,假設他是透過jar包引入的依賴,LogDemo類,是我們實際卡法的程式碼。
  • Log是我們用來打日誌的介面,jar包中有Log4j,Log4j2和LogBack實現。
  • Log5MuMu是呼叫方自己的日誌介面實現。

現在想實現如下功能,在介面呼叫方,可以靈活的配置要使用的日誌實現,且可以動態替換成自己的實現Log4MuMu,這裡就用到SPI。

程式碼實現

  1. LogDemo的具體程式碼

  2. Log介面

  3. LogUtil具體的實現

  4. jar包中的Log介面實現
    Log4j實現

    LogBack實現

  5. 呼叫方的介面實現
    Log4MuMu實現

  6. 配置檔案
    目錄是META-INF/services,檔名和要載入的介面的全限定性類名保持一致

    配置內容是介面對應實現的全限定性類名

    當配置檔案配置的是Log4MuMu,執行結果如下

    修改配置檔案為Log4j

bingo,我們實現了可以在不修改程式碼的情況下,僅修改配置檔案中的類名,就可以動態的替換列印日誌的實現類。那麼具體是怎麼實現的類,接下來我們看看JDK中具體實現SPI的核心原始碼。

SPI原始碼解析

下圖是SPI的執行機制

JDK中SPI的實現,主要依賴ServiceLoader及其內部類LazyIterator

ServiceLoader類的核心結構

ServiceLoader實現的採用的是懶載入,具體實現在LazyIterator中,在實際迭代中會進行類的載入和例項化物件

LazyIterator類的核心方法

LazyIterator類實現了Iterator介面,採用的懶載入方式,在迭代過程中去記載具體的類和實現。

hasNext方法呼叫的是hasNextService 方法,用來載入配置檔案中的全限定性類名

next方法呼叫的是nextService方法,用來例項化配置檔案中的類,並進行快取

解析配置檔案parse方法

parse方法是解析配置檔案的具體實現

JAVA實現的SPI,配置檔案要滿足下面幾條

  1. 檔案位於META-INF/services/下
  2. 檔名就是介面的全限定性類名
  3. 配置檔案內容是需要載入的配置的實現類的全限定性類名
  4. 介面實現按行配置,可以是多個。如果包含#號,每一行只取第一個#號前的內容。

JDBC中載入資料庫驅動

以下是JDBC中載入資料庫驅動的核心類DriverManager,其主要邏輯loadInitialDrivers方法也是使用了ServiceLoader,來實現資料庫驅動的熱插拔。

SPI和API的區別

API(Application Programming Interface)和SPI(Service Provider Interface)都是軟體設計中用於定義元件間互動的介面,但它們有著不同的定義和用途。

API 是應用程式之間的介面,規定了不同元件之間如何進行功能呼叫。它提供了一組預定義的類和方法,開發者可以基於這些介面來完成特定的任務或呼叫功能。API的主要目的是透過標準化介面來實現模組之間的互操作,強調的是呼叫者和被呼叫者之間的契約。

SPI 是一種允許服務提供者擴充套件和替換應用程式的核心部件或功能的介面。它常見於框架和庫,允許開發者插入自定義實現。SPI的主要目的是提供擴充套件點,框架開發者透過定義介面,允許服務提供者實現這些介面,從而在不修改框架核心程式碼的情況下擴充套件功能,強調的是實現提供者和框架之間的松耦合

區別

  1. 針對物件不同:API 通常是面向終端使用者或外部系統的,提供了可直接使用的功能;而SPI 更多是面向系統開發者,為他們提供一種將新服務或外掛加入系統的方式。
  2. 目的不同:API的主要目的是提供介面供外界訪問和使用特定的功能或資料;SPI則是為了提供一個標準,允許第三方開發者實現並插入新的服務。
  3. 定義方式不同:API 是由開發者主動編寫並公開給其他開發者使用的,而 SPI 是由框架或庫提供方定義的介面,供第三方開發者實現。
  4. 呼叫方式不同:API 是透過直接呼叫介面的方法來使用功能,而 SPI 是透過配置檔案來指定具體的實現類,然後由框架或庫自動載入和呼叫。
  5. 靈活性不同:API 的實現類必須在編譯時就確定,無法動態替換;而 SPI 的實現類可以在執行時根據配置檔案的內容進行動態載入和替換。
  6. 依賴關係不同:API 是被呼叫方依賴的,即應用程式需要引入 API 所在的庫才能使用其功能;而 SPI 是呼叫方依賴的,即框架或庫需要引入第三方實現類的庫才能載入和呼叫。

總的來說,API 和 SPI 都是軟體開發中的介面,但API更多是面向外部使用,而SPI則是面向內部擴充套件。在實踐中,一個服務可能同時提供API和SPI,API用於呼叫服務,而SPI用於擴充套件服務。

寫在最後

SPI到底是什麼

SPI全稱為Service Provider Interface,是一種服務發現機制。它透過在ClassPath路徑下的資料夾查詢檔案,自動載入檔案裡所定義的類並例項化。

SPI的核心思想 - 解耦&OCP

Java SPI實際上是“基於介面的程式設計+策略模式+配置檔案”組合實現的動態載入機制,核心目的是解耦。

SPI符合程式設計的開閉原則

PS:開閉原則(Open Close Principle),簡稱OCP,是物件導向設計中重要的原則,它要求軟體實體對擴充套件開放,對修改封閉,軟體實體包含函式、類、模組甚至是可執行程式。

JDK中SPI存在的問題

①介面的實現類,全都會被例項化一遍造成資源的浪費
②獲取實現類,只能透過Iterator,不夠靈活

上面的兩個問題在Dubbo的SPI中都進行了解決,後續將出一篇文章進行講解,敬請期待~~


歡迎大家點贊-評論-關注,關注【程式設計師木木熊】,瞭解更多後端技術知識!!

也可以微信搜尋程式設計師木木熊,海量Java、架構、面試、演算法資料免費送,拉你進入技術交流社群,共同學習進步~~

相關文章