深入剖析 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() 方法實現對服務提供接口的查找,最後遍歷來逐個訪問服務提供接口的實現類。
從源碼可以發現:
-
ServiceLoader 類本身實現了 Iterable 接口並實現了其中的 iterator 方法,iterator 方法的實現中調用了 LazyIterator 這個內部類中的方法,迭代器創建實例。
-
所有服務提供接口的對應文件都是放置在 META-INF/services / 目錄下,final 類型決定了 PREFIX 目錄不可變更。
雖然 java 提供的 SPI 機制的思想非常好,但是也存在相應的弊端。具體如下:
-
Java 內置的方法方式只能通過遍歷來獲取
-
服務提供接口必須放到 META-INF/services / 目錄下。
針對 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 進行的相關優化具體內容如下:
-
Java SPI 是一個服務提供接口對應一個配置文件,配置文件中存放當前接口的所有實現類,多個服務提供接口對應多個配置文件,所有配置都在 services 目錄下;關注公衆號:碼猿技術專欄,回覆關鍵詞:1111 獲取阿里內部 Java 性能調優手冊!
-
Spring factories SPI 是一個 spring.factories 配置文件存放多個接口及對應的實現類,以接口全限定名作爲 key,實現類作爲 value 來配置,多個實現類用逗號隔開,僅 spring.factories 一個配置文件。
那麼 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