SPI 實現原理及運用


SPI原理

 

SPI的全名為Service Provider Interface.大多數開發人員可能不熟悉,因為這個是針對廠商或者插件的。在java.util.ServiceLoader的文檔里有比較詳細的介紹。簡單的總結下java spi機制的思想。我們系統里抽象的各個模塊,往往有很多不同的實現方案,比如日志模塊的方案,xml解析模塊、jdbc模塊的方案等。面向對象的設計里,我們一般推薦模塊之間基於接口編程,模塊之間不使用實現類進行硬編碼。一旦代碼里涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。為了實現在模塊裝配的時候動態指定具體實現類,這就需要一種服務發現機制。 java spi就是提供這種功能的機制:為某個接口尋找服務實現的機制。有點類似IOC的思想,將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。 

java SPI應用場景很廣泛,在Java底層和一些框架中都很常用,比如java數據驅動加載和Dubbo。Java底層定義加載接口后,由不同的廠商提供驅動加載的實現方式,當我們需要加載不同的數據庫的時候,只需要替換數據庫對應的驅動加載jar包,就可以進行使用。

 

要使用Java SPI,需要遵循如下約定:

  • 1、當服務提供者提供了接口的一種具體實現后,在jar包的META-INF/services目錄下創建一個以“接口全限定名”為命名的文件,內容為實現類的全限定名;

  • 2、接口實現類所在的jar包放在主程序的classpath中;

  • 3、主程序通過java.util.ServiceLoder動態裝載實現模塊,它通過掃描META-INF/services目錄下的配置文件找到實現類的全限定名,把類加載到JVM;

  • 4、SPI的實現類必須攜帶一個不帶參數的構造方法;

SPI實現

1. 建立maven工程

(1)spi-demo-api 提供需要實現的接口

public interface SpiDemo {
    void say();
}

(2)spi-demo-impl1 為第一種實現

public class SpiDemoImpl1 implements SpiDemo {
    public void say() {
        System.out.println("SpiDemoImpl1");
    }
}

每一個SPI接口都需要在自己項目的靜態資源目錄中聲明一個services文件,文件名為實現規范接口的類名全路徑。在resources目錄中創建\META-INF\services目錄,創建以com.hanggle.spi.api.SpiDemo為名的文件。(文件名即是要實現的接口類的全路徑如下圖)

 

文件內容:

com.hanggle.spi.api.impl1.SpiDemoImpl1

 

(3)spi-demo-impl2 為第二種實現 

public class SpiDemoImpl2 implements SpiDemo {
    public void say() {
        System.out.println("SpiDemoImpl2");
    }
}

同spi-demo-impl1一樣

在resources目錄中創建\META-INF\services目錄,創建以com.hanggle.spi.api.SpiDemo為名的文件。

 文件內容:

com.hanggle.spi.api.impl1.SpiDemoImpl2

 

(4)spi-demo-main 程序中的使用

public static void main(String[] args) {
        ServiceLoader<SpiDemo> serviceLoader = ServiceLoader.load(SpiDemo.class);
        for (SpiDemo o : serviceLoader) {
            o.say();
        }
    }

運行結果:

 

 SPI 源碼

裝配文件路徑的定義:

有源代碼可以,java會根據定義的路徑去掃描可能存在的接口的實現。放在config中,然后使用parse方法將配置文件中的接口實現全路徑放在pending中,並取得第一個實現類(變量nextName),

然后使用類加載器加載,加載需要調用的類,然后調用實現的方法

 

優缺點

優點:

使用Java SPI機制的優勢是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起。應用程序可以根據實際業務情況啟用框架擴展或替換框架組件。

 

缺點:

  • 雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載並實例化一遍。如果你並不想用某些實現類,它也被加載並實例化了,這就造成了浪費。獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類。

  • 多個並發多線程使用ServiceLoader類的實例是不安全的。

 

 參考:http://www.pandan.xyz/2017/03/14/java%20SPI%E6%9C%BA%E5%88%B6%E5%8E%9F%E7%90%86/

https://yq.aliyun.com/articles/640161


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM