SPI的全名爲Service Provider Interface.大多數開發人員可能不熟悉,由於這個是針對廠商或者插件的。在java.util.ServiceLoader的文檔裏有比較詳細的介紹。簡單的總結下java spi機制的思想。咱們系統裏抽象的各個模塊,每每有不少不一樣的實現方案,好比日誌模塊的方案,xml解析模塊、jdbc模塊的方案等。面向對象的設計裏,咱們通常推薦模塊之間基於接口編程,模塊之間不使用實現類進行硬編碼。一旦代碼裏涉及具體的實現類,就違反了可拔插的原則,若是須要替換一種實現,就須要修改代碼。爲了實如今模塊裝配的時候動態指定具體實現類,這就須要一種服務發現機制。 java spi就是提供這種功能的機制:爲某個接口尋找服務實現的機制。有點相似IOC的思想,將裝配的控制權移到程序以外,在模塊化設計中這個機制尤爲重要。 java
java SPI應用場景很普遍,在Java底層和一些框架中都很經常使用,好比java數據驅動加載和Dubbo。Java底層定義加載接口後,由不一樣的廠商提供驅動加載的實現方式,當咱們須要加載不一樣的數據庫的時候,只須要替換數據庫對應的驅動加載jar包,就能夠進行使用。數據庫
要使用Java SPI,須要遵循以下約定:編程
一、當服務提供者提供了接口的一種具體實現後,在jar包的META-INF/services目錄下建立一個以「接口全限定名」爲命名的文件,內容爲實現類的全限定名;api
二、接口實現類所在的jar包放在主程序的classpath中;安全
三、主程序經過java.util.ServiceLoder動態裝載實現模塊,它經過掃描META-INF/services目錄下的配置文件找到實現類的全限定名,把類加載到JVM;多線程
四、SPI的實現類必須攜帶一個不帶參數的構造方法;併發
public interface SpiDemo { void say(); }
public class SpiDemoImpl1 implements SpiDemo { public void say() { System.out.println("SpiDemoImpl1"); } }
每個SPI接口都須要在本身項目的靜態資源目錄中聲明一個services文件,文件名爲實現規範接口的類名全路徑。在resources目錄中建立\META-INF\services目錄,建立以com.hanggle.spi.api.SpiDemo爲名的文件。(文件名便是要實現的接口類的全路徑以下圖)框架
文件內容:maven
com.hanggle.spi.api.impl1.SpiDemoImpl1
public class SpiDemoImpl2 implements SpiDemo { public void say() { System.out.println("SpiDemoImpl2"); } }
同spi-demo-impl1同樣ide
在resources目錄中建立\META-INF\services目錄,建立以com.hanggle.spi.api.SpiDemo爲名的文件。
文件內容:
com.hanggle.spi.api.impl1.SpiDemoImpl2
public static void main(String[] args) { ServiceLoader<SpiDemo> serviceLoader = ServiceLoader.load(SpiDemo.class); for (SpiDemo o : serviceLoader) { o.say(); } }
運行結果:
裝配文件路徑的定義:
有源代碼能夠,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/