在Dubbo中,SPI是一個非常核心的機制,貫穿在幾乎所有的流程中。

Java的SPI

SPI全稱(service provider interface),是JDK內建的一種服務提供發現機制,目前市面上有很多框架都是用它來做服務的擴充套件發現,大家耳熟能詳的如JDBC、日誌框架都有用到。

SPI具體約定: Java SPI的具體約定為:當服務的提供者,提供了服務介面的一種實現之後,在jar包的META-INF/services/目錄裡同時建立一個以服務介面命名的檔案。該檔案裡就是實現該服務介面的具體實現類。而當外部程式裝配這個模組的時候,就能通過該jar包META-INF/services/裡的配置檔案找到具體的實現類名,並裝載例項化,完成模組的注入。 基於這樣一個約定就能很好的找到服務介面的實現類,而不需要再程式碼裡制定。jdk提供服務實現查詢的一個工具類:java.util.ServiceLoader。

演示示例

官方定義了資料庫驅動規範API,各大資料庫廠商自己定義自己的規範實現:

首先定義一個官方規範API:

將jar安裝到本地倉庫:

MySql廠商依賴官方規範API:

根據SPI規範,先新建一個resources,在resource下新建一個META-INF目錄,在META-INF目錄下建立一個services目錄,在services下新建一個檔名為DataBaseDriver全類名一樣的檔案:

 

在dongguabai.dubbo.spi.DataBaseDriver檔案中增加實現類的全類名:

在應用中新增MySql規範的依賴:

接下來就可以使用驅動了:

package test.demo2;

import com.spi.DataBaseDriver;

import java.util.ServiceLoader;

/**
 * Hello world!
 */
public class App {
    public static void main(String[] args) {
        ServiceLoader<DataBaseDriver> loader = ServiceLoader.load(DataBaseDriver.class);
        for (DataBaseDriver driver : loader) {
            System.out.println(driver.connect("127.0.0.1"));
        }
    }
}

執行結果:

接下來實現一個Oracle的驅動工程:

同樣需要引入官方規範的依賴:

Oracle的實現:

新增相應的META-INF檔案:

接下來打包。

在應用中使用也很方便,剛剛依賴的是MySql的包,直接替換成Oracle的就行了:

執行程式碼不用變:

可以看出基於SPI我們可以非常方便的實現可插拔式的擴充套件。

也許會有人說,那如果我兩個都要用怎麼辦,那就兩個都依賴即可:

發現兩個都出現了,也就是都載入了。這個的確是一個問題,這個在後面再詳細說明。

再來總結一下SPI規範實現:

  1. 需要在classpath下建立一個目錄,該目錄命名必須是:META-INF/services
  2. 在該目錄下建立一個properties檔案,該檔案需要滿足以下幾個條件
    1. 檔名必須是擴充套件的介面的全路徑名稱
    2. 檔案內部描述的是該擴充套件介面的所有實現類
    3. 檔案的編碼格式是UTF-8
  3. 通過java.util.ServiceLoader的載入機制來發現

SPI的實際應用

SPI在很多地方有應用,比如最常用的java.sql.Driver驅動。JDK官方提供了java.sql.Driver這個驅動擴充套件點,但是並沒有看到JDK中有對應的Driver實現:

 

這裡依賴一個MySql的實現:

可以發現在META-INF檔案下有services/java.sql.Driver檔案:

實現類為:

SPI的缺點

1. JDK標準的SPI會一次性載入例項化擴充套件點的所有實現,什麼意思呢?就是如果你在META-INF/service下的檔案裡面加了N個實現類,那麼JDK啟動的時候都會一次性全部載入。那麼如果有的擴充套件點實現初始化很耗時或者如果有些實現類並沒有用到,那麼會很浪費資源。

2. 如果擴充套件點載入失敗,會導致呼叫方報錯,而且這個錯誤很難定位到是這個原因。

參考資料:

https://blog.csdn.net/sigangjun/article/details/79071850