源碼級深度理解 Java SPI

作者:vivo 互聯網服務器團隊- Zhang Peng
SPI 是一種用于動態加載服務的機制 。它的核心思想就是解耦,屬于典型的微內核架構模式 。SPI 在 Java 世界應用非常廣泛,如:Dubbo、Spring Boot 等框架 。本文從源碼入手分析,深入探討 Java SPI 的特性、原理 , 以及在一些比較經典領域的應用 。
一、SPI 簡介SPI 全稱 Service Provider Interface,是 Java 提供的,旨在由第三方實現或擴展的 API , 它是一種用于動態加載服務的機制 。Java 中 SPI 機制主要思想是將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要,其核心思想就是 解耦 。
Java SPI 有四個要素:
  • SPI 接口:為服務提供者實現類約定的的接口或抽象類 。
  • SPI 實現類:實際提供服務的實現類 。
  • SPI 配置:Java SPI 機制約定的配置文件 , 提供查找服務實現類的邏輯 。配置文件必須置于 META-INF/services 目錄中,并且,文件名應與服務提供者接口的完全限定名保持一致 。文件中的每一行都有一個實現服務類的詳細信息,同樣是服務提供者類的完全限定名稱 。
  • ServiceLoader:Java SPI 的核心類,用于加載 SPI 實現類 。ServiceLoader 中有各種實用方法來獲取特定實現、迭代它們或重新加載服務 。
二、SPI 示例正所謂,實踐出真知,我們不妨通過一個具體的示例來看一下,如何使用 Java SPI 。
2.1 SPI 接口首先,需要定義一個 SPI 接口 , 和普通接口并沒有什么差別 。
package io.github.dunwu.javacore.spi;public interface DataStorage {String search(String key);}2.2 SPI 實現類假設,我們需要在程序中使用兩種不同的數據存儲——MySQL 和 Redis 。因此 , 我們需要兩個不同的實現類去分別完成相應工作 。
MySQL查詢 MOCK 類
package io.github.dunwu.javacore.spi;public class MysqlStorage implements DataStorage {@Overridepublic String search(String key) {return "【Mysql】搜索" + key + ",結果:No";}}Redis 查詢 MOCK 類
package io.github.dunwu.javacore.spi;public class RedisStorage implements DataStorage {@Overridepublic String search(String key) {return "【Redis】搜索" + key + ",結果:Yes";}}service 傳入的是期望加載的 SPI 接口類型 到目前為止,定義接口,并實現接口和普通的 Java 接口實現沒有任何不同 。
2.3 SPI 配置如果想通過 Java SPI 機制來發現服務,就需要在 SPI 配置中約定好發現服務的邏輯 。配置文件必須置于 META-INF/services 目錄中,并且 , 文件名應與服務提供者接口的完全限定名保持一致 。文件中的每一行都有一個實現服務類的詳細信息,同樣是服務提供者類的完全限定名稱 。以本示例代碼為例,其文件名應該為io.github.dunwu.javacore.spi.DataStorage , 
文件中的內容如下:
io.github.dunwu.javacore.spi.MysqlStorageio.github.dunwu.javacore.spi.RedisStorage2.4 ServiceLoader完成了上面的步驟,就可以通過 ServiceLoader 來加載服務 。示例如下:
import java.util.ServiceLoader;public class SpiDemo {public static void main(String[] args) {ServiceLoader<DataStorage> serviceLoader = ServiceLoader.load(DataStorage.class);System.out.println("============ Java SPI 測試============");serviceLoader.forEach(loader -> System.out.println(loader.search("Yes Or No")));}}輸出:
============ Java SPI 測試============【Mysql】搜索Yes Or No , 結果:No【Redis】搜索Yes Or No,結果:Yes三、SPI 原理上文中,我們已經了解 Java SPI 的要素以及使用 Java SPI 的方法 。你有沒有想過,Java SPI 和普通 Java 接口有何不同 , Java SPI 是如何工作的 。實際上,Java SPI 機制依賴于 ServiceLoader 類去解析、加載服務 。因此,掌握了 ServiceLoader 的工作流程,就掌握了 SPI 的原理 。ServiceLoader 的代碼本身很精練 , 接下來,讓我們通過走讀源碼的方式,逐一理解 ServiceLoader 的工作流程 。
3.1 ServiceLoader 的成員變量先看一下 ServiceLoader 類的成員變量 , 大致有個印象,后面的源碼中都會使用到 。
public final class ServiceLoader<S> implements Iterable<S> {// SPI 配置文件目錄private static final String PREFIX = "META-INF/services/";// 將要被加載的 SPI 服務private final Class<S> service;// 用于加載 SPI 服務的類加載器private final ClassLoader loader;// ServiceLoader 創建時的訪問控制上下文private final AccessControlContext acc;// SPI 服務緩存,按實例化的順序排列private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 懶查詢迭代器private LazyIterator lookupIterator;// ...}

推薦閱讀