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/