深入剖析 Spring Boot 的 SPI 機制

大家好,我是不才陳某~

SPI(Service Provider Interface) 是 JDK 內置的一種服務提供發現機制,可以用來啓用框架擴展和替換組件, 主要用於框架中開發,例如 Dubbo、Spring、Common-Logging,JDBC 等採用採用 SPI 機制,針對同一接口採用不同的實現提供給不同的用戶,從而提高了框架的擴展性。

之前也寫過 Java SPI 的深入剖析:聊聊 Java SPI 機制

推薦 Java 工程師技術指南:https://github.com/chenjiabing666/JavaFamily

Java SPI 實現

Java 內置的 SPI 通過 java.util.ServiceLoader 類解析 classPath 和 jar 包的 META-INF/services / 目錄 下的以接口全限定名命名的文件,並加載該文件中指定的接口實現類,以此完成調用。

示例說明

創建動態接口

public interface VedioSPI
{
    void call();
}

實現類 1

public class Mp3Vedio implements VedioSPI
{
    @Override
    public void call()
    {
        System.out.println("this is mp3 call");
    }

}

實現類 2

public class Mp4Vedio implements VedioSPI
{
    @Override
    public void call()
    {
       System.out.println("this is mp4 call");
    }

}

在項目的 source 目錄下新建 META-INF/services / 目錄下,創建 com.skywares.fw.juc.spi.VedioSPI 文件。

相關測試

public class VedioSPITest
{
    public static void main(String[] args)
    {
        ServiceLoader<VedioSPI> serviceLoader =ServiceLoader.load(VedioSPI.class);
        
        serviceLoader.forEach(t->{
            t.call();
        });
    }
}

說明:Java 實現 spi 是通過 ServiceLoader 來查找服務提供的工具類。

運行結果:

源碼分析

上述只是通過簡單的示例來實現下 java 的內置的 SPI 功能。其實現原理是 ServiceLoader 是 Java 內置的用於查找服務提供接口的工具類,通過調用 load() 方法實現對服務提供接口的查找,最後遍歷來逐個訪問服務提供接口的實現類。

從源碼可以發現:

雖然 java 提供的 SPI 機制的思想非常好,但是也存在相應的弊端。具體如下:

針對 java 的 spi 存在的問題,Spring 的 SPI 機制沿用的 SPI 的思想,但對其進行擴展和優化。

推薦 Java 工程師技術指南:https://github.com/chenjiabing666/JavaFamily

Spring SPI

Spring SPI 沿用了 Java SPI 的設計思想,Spring 採用的是 spring.factories 方式實現 SPI 機制,可以在不修改 Spring 源碼的前提下,提供 Spring 框架的擴展性。

Spring 示例

定義接口

public interface DataBaseSPI
{
   void getConnection();
}

相關實現

##DB2實現
public class DB2DataBase implements DataBaseSPI
{
    @Override
    public void getConnection()
    {
        System.out.println("this database is db2");
    }

}

##Mysql實現
public class MysqlDataBase implements DataBaseSPI
{
    @Override
    public void getConnection()
    {
       System.out.println("this is mysql database");
    }

}

1、在項目的 META-INF 目錄下,新增 spring.factories 文件

2、填寫相關的接口信息,內容如下:

com.skywares.fw.juc.springspi.DataBaseSPI = com.skywares.fw.juc.springspi.DB2DataBase, com.skywares.fw.juc.springspi.MysqlDataBase

說明多個實現採用逗號分隔。

相關測試類

public class SpringSPITest
{
    public static void main(String[] args)
    {
         List<DataBaseSPI> dataBaseSPIs =SpringFactoriesLoader.loadFactories(DataBaseSPI.class, 
                 Thread.currentThread().getContextClassLoader());
         
         for(DataBaseSPI datBaseSPI:dataBaseSPIs){
            datBaseSPI.getConnection();
         }
    }
}

輸出結果

從示例中我們看出,Spring 採用 spring.factories 實現 SPI 與 java 實現 SPI 非常相似,但是 spring 的 spi 方式針對 java 的 spi 進行的相關優化具體內容如下:

那麼 spring 是如何通過加載 spring.factories 來實現 SpI 的呢? 我們可以通過源碼來進一步分析。

推薦 Java 工程師技術指南:https://github.com/chenjiabing666/JavaFamily

源碼分析

說明: loadFactoryNames 解析 spring.factories 文件中指定接口的實現類的全限定名,具體實現如下:

說明:獲取所有 jar 包中 META-INF/spring.factories 文件路徑,以枚舉值返回。遍歷 spring.factories 文件路徑,逐個加載解析,整合 factoryClass 類型的實現類名稱,獲取到實現類的全類名稱後進行類的實例話操作,其相關源碼如下:

說明:實例化是通過反射來實現對應的初始化。

https://juejin.cn/post/7132742686099898398

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GLX1OrjzO2VKMx-WVSazSg