Java SPI機制,你瞭解過嗎?

碼農Amg發表於2021-12-10

Life moves pretty fast,if you don't stop and look around once in a while,you will miss it

為什麼需要SPI?

思考一個場景,我們封裝了一套服務,別人通過引入我們寫好的包,就可以使用這些介面API,完成相應的操作,這本來沒有什麼問題,但是會存在使用該服務的實體有不相同的業務需求,需要進一步的擴充套件,但是由於api是寫好的,想要擴充套件並非那麼的簡單,如果存在這樣子的場景,我們該怎麼辦?

可以使用Java 提供的SPI機制

什麼是SPI?SPI和API的區別

  • SPI

    SPI的全稱是Service Provider Interface,是Java提供的可用於第三方實現和擴充套件的機制,通過該機制,我們可以實現解耦,SPI介面方負責定義和提供預設實現,SPI呼叫方可以按需擴充套件

  • API的全稱是Application Programming Interface,廣義上來看就是介面,負責程式與程式之間進行協作的通道,就好比上面給的例子,【我們封裝好了一套服務,通過API的形式提供給他人使用,別人使用API就能得到想要的】

所以他們倆的區別就很明顯了,API的呼叫方只能依賴使用提供方的實現,SPI就如同可定製化的API一樣,呼叫方可以自定義實現替換API提供的預設實現

來人,上點對抗

首先,我們新建一個空的maven專案,裡邊有兩個包

  • spi-provider 從名字就可以得知是SPI的提供方
  • spi-user SPI的使用方

image-20211209183930000

spi-provider

我們簡單定義一個SPI介面,就叫ISpiTest,裡邊就一個saySomething方法,再提供一個預設的實現

public interface ISpiTest {
	void saySomething();
}

public class DefaultSpiImplementation implements ISpiTest{
	@Override
	public void saySomething() {
		System.out.println("[預設實現] -> 今天也是充滿希望的一天");
	}
}

然後,模擬走流程,注意步驟4是我們之後要自定義替換的

/**
 * 模擬一套流程
 * @author Amg
 * @date 2021/12/9
 */
public class TestUtils {
	
	public static void workFlow(ISpiTest s) {
		
		System.out.println("1、步驟1.......");
		System.out.println("2、步驟2.......");
		System.out.println("3、步驟3.......");
		System.out.print("4、步驟4:");
		s.saySomething();
		System.out.println("5、步驟5.......");
	}
}

接著,重點來了,我們需要在resources目錄下面建立/META-INF/services資料夾然後以SPI介面的全限定類名作為名稱建立一個檔案

image-20211209234205568

往檔案裡面填寫實現類的全限定類名,如下

com.amg.spi.DefaultSpiImplementation

到此,spi-provider這個模組就完成了,至於之後要怎麼使用,到spi-user模組中進一步說明

spi-user

首先,我們在pom檔案中,引入spi-provider座標依賴

image-20211209234727579

​ 然後定義main方法,在main方法中呼叫在spi-provider中定義的SPI介面,此時採用的是預設的配置

image-20211209235028763

可以注意到我們使用ServiceLoader這個類的load方法,傳入SPI介面的位元組碼進行構造,我們在spi-provider中resources中給出了一個預設實現,但是我們是在spi-user中去呼叫的,ServiceLoader會自動讀取META-INF下的配置檔案,就算是跨jar包也是可以的

然後現在我們在spi-user中定義一個實現類,以及把他配置到META-INF下(需要注意,這個配置的全限定類名仍然需要是spi-provider中定義SPI介面的路徑),來看看效果

image-20211210093427709

spi-user下META-INF裡邊內容如下

com.amg.spiuser.service.impl.WantHamburger

image-20211210093641053

可以發現,我們並沒有改變任何的客戶端程式碼,只是把配置檔案進行了簡單的修改,即可完成自定義實現,這就是使用SPI的魅力

?思考一下,我們之前的流程是怎麼做的

  • 首先定義了一個介面,面向介面程式設計嘛
  • 定義配置檔案
  • 各個自定義的實現類,只需要按照規則重寫配置檔案即可

總結

通過這個流程,我們可以歸納為一句話,SPI是策略模式的一種體現,配合面向介面程式設計的思想以及必要的配置檔案,即可完成定義和具體實現的解耦,而且是可定製化的API

SPI的優點有以下

  • 定製化實現介面
  • 解耦

SPI的缺點有以下

  • 通過觀察ServiceLoader,可以發現並沒有額外的加鎖機制,所以會存在併發問題
  • 獲取對應的實現類不夠靈活,從上面例子可以看出,需要使用迭代器的方式獲取
  • 需要知道介面的所有具體實現類,所以每次都要載入和例項化所有的實現類

實際中,SPI的使用還是很常見的,例如Dubbo和Spring Boot都為我們提供了一套SPI機制,只不過此SPI是在Java提供的SPI機制基礎上進行改造而來,有興趣的同學也可以去查下資料,增長增長

好啦,本期的文章就到這裡,限於本人水平的問題,如有寫得不對的地方,歡迎指出更正,謝謝!

相關文章