深入解析Spring Boot 的SPI機制詳情

簡介

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

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來查找服務提供的工具類。

運行結果

圖片.png

源碼分析

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

從源碼可以發現:

  • ServiceLoader類本身實現瞭Iterable接口並實現瞭其中的iterator方法,iterator方法的實現中調用瞭LazyIterator這個內部類中的方法,解析完服務提供接口文件後最終結果放在瞭Iterator中返回,並不支持服務提供接口實現類的直接訪問。
  • 所有服務提供接口的對應文件都是放置在META-INF/services/目錄下,final類型決定瞭PREFIX目錄不可變更。

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

  • Java內置的方法方式隻能通過遍歷來獲取
  • 服務提供接口必須放到META-INF/services/目錄下。

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

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進行的相關優化具體內容如下:

  • Java SPI是一個服務提供接口對應一個配置文件,配置文件中存放當前接口的所有實現類,多個服務提供接口對應多個配置文件,所有配置都在services目錄下;
  • Spring factories SPI是一個spring.factories配置文件存放多個接口及對應的實現類,以接口全限定名作為key,實現類作為value來配置,多個實現類用逗號隔開,僅spring.factories一個配置文件。

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

源碼分析

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

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

其相關源碼如下:

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

總結

本文詳細的講解瞭java和Spring的SPI機制,SPI技術將服務接口與服務實現進行分離實現解耦,從而提升程序的可擴展性。如有疑問,請隨時反饋。

到此這篇關於深入解析Spring Boot 的SPI機制詳情的文章就介紹到這瞭,更多相關Spring Boot SPI機制內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: