SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現或者擴展的API,它可以用來啟用框架擴展和替換組件。
常見的 SPI 有 JDBC、日志門面接口、Spring、SpringBoot相關starter組件、Dubbo、JNDI等。
Java SPI 實際上是“基於接口的編程+策略模式+配置文件”組合實現的動態加載機制。
要使用Java SPI,需要遵循如下約定:
1、當服務提供者提供了接口的一種具體實現后,在jar包的META-INF/services目錄下創建一個以“接口全限定名”為命名的文件,內容為實現類的全限定名;
2、接口實現類所在的jar包放在主程序的classpath中;
3、主程序通過java.util.ServiceLoder動態裝載實現模塊,它通過掃描META-INF/services目錄下的配置文件找到實現類的全限定名,把類加載到JVM;
4、SPI的實現類必須攜帶一個不帶參數的構造方法。
示例:
先創建四個maven項目,分別為spi-inter(定義標准服務接口)、spi-defaultInterImpl(服務提供方提供的默認實現)、spi-myInterImpl(調用方覺得默認實現不好使,自己寫的實現)、spi-testSpi。
在spi-inter 中定義標准服務接口, 然后將項目打包。
1 package com.demo.spi_inter; 2 /** 3 * 服務方定義的標准服務接口 4 */ 5 public interface Config { 6 7 void loadConfig(); 8 }
在spi-defaultInterImpl的pom.xml中添加依賴spi-inter,並進行對接口的實現。在src/main/java目錄下新建META-INF/services目錄,並在services中新建文件,文件名為接口的全限定名,
如示例:“com.demo.spi_inter.Config”,內容為接口實現類的全限定名。如示例:”com.demo.spi_defaultInterImpl.DefaultInterImpl“。然后將項目打包。
1 package com.demo.spi_defaultInterImpl; 2 3 import com.demo.spi_inter.Config; 4 5 public class DefaultInterImpl implements Config{ 6 7 @Override 8 public void loadConfig() { 9 System.out.println("這是對接口的默認實現"); 10 } 11 12 }
在spi-testSpi的pom.xml文件中添加入spi-defaultInterImpl的依賴。使用 ServiceLoader 來加載配置文件中指定的實現。
1 package com.demo.spi_testSpi; 2 3 import java.util.Iterator; 4 import java.util.ServiceLoader; 5 6 import com.demo.spi_inter.Config; 7 8 public class App { 9 public static void main(String[] args) { 10 System.out.println("開始加載"); 11 ServiceLoader<Config> loader = ServiceLoader.load(Config.class); 12 Iterator<Config> iterator = loader.iterator(); 13 while (iterator.hasNext()) { 14 Config config = (Config) iterator.next(); 15 config.loadConfig(); 16 } 17 } 18 }
運行,可得結果:
此時,個人調用方覺得默認的實現不太好,於是自己編寫了一個新的實現spi-myInterImpl。並創建了相對應的目錄結構及文件/META-INF/services/com.demo.spi_inter.Config,文件內容為個人實現的全限定名。打包,將依賴引入到spi-testSpi中。
1 package com.demo.spi_myInterImpl; 2 3 import com.demo.spi_inter.Config; 4 5 public class MyInterImpl implements Config{ 6 7 @Override 8 public void loadConfig() { 9 System.out.println("這是個人寫的實現"); 10 } 11 }
運行spi-testSpi,可以看見控制台輸出:
SPI技術的優劣:
優點:
解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起。應用程序可以根據實際業務情況啟用框架擴展或替換框架組件。
缺點:
雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載並實例化一遍。如果你並不想用某些實現類,它也被加載並實例化了,這就造成了浪費。獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類。
多個並發多線程使用ServiceLoader類的實例是不安全的。