0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
电子发烧友
开通电子发烧友VIP会员 尊享10大特权
海量资料免费下载
精品直播免费看
优质内容免费畅学
课程9折专享价
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

可插拔組件設計機制—SPI介紹

OSC開源社區(qū) ? 來源:OSCHINA 社區(qū) ? 2023-03-23 09:20 ? 次閱讀

1.SPI 是什么?

SPI 的全稱是 Service Provider Interface, 即提供服務接口;是一種服務發(fā)現(xiàn)機制,SPI 的本質是將接口實現(xiàn)類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現(xiàn)類。這樣可以在運行時,動態(tài)為接口替換實現(xiàn)類。正因此特性,我們可以很容易的通過 SPI 機制為我們的程序提供拓展功能。 如下圖:

a56e4b2a-c8f0-11ed-bfe3-dac502259ad0.png

系統(tǒng)設計的各個抽象,往往有很多不同的實現(xiàn)方案,在面對象設計里,一般推薦模塊之間基于接口編程,模塊之間不對實現(xiàn)硬編碼,一旦代碼涉及具體的實現(xiàn)類,就違反了可插拔的原則。Java SPI 就是提供這樣的一個機制,為某一個接口尋找服務的實現(xiàn),有點類似 IOC 的思想,把裝配的控制權移到程序之外,在模塊化涉及里面這個各尤為重要。與其說 SPI 是 java 提供的一種服務發(fā)現(xiàn)機制,倒不如說是一種解耦思想。

2. 使用場景

數(shù)據(jù)庫驅動加載接口實現(xiàn)類的加載;如:JDBC 加載 Mysql,Oracle...

日志門面接口實現(xiàn)類加載,如:SLF4J 對 log4j、logback 的支持

Spring 中大量使用了 SPI,特別是 spring-boot 中自動化配置的實現(xiàn)

Dubbo 也是大量使用 SPI 的方式實現(xiàn)框架的擴展,它是對原生的 SPI 做了封裝,允許用戶擴展實現(xiàn) Filter 接口。

3. 使用介紹

要使用 Java SPI,需要遵循以下約定:

當服務提供者提供了接口的一種具體實現(xiàn)后,需要在 JAR 包的 META-INF/services 目錄下創(chuàng)建一個以 “接口全限制定名” 為命名的文件,內(nèi)容為實現(xiàn)類的全限定名;

接口實現(xiàn)類所在的 JAR 放在主程序的 classpath 下,也就是引入依賴。

主程序通過 java.util.ServiceLoder 動態(tài)加載實現(xiàn)模塊,它會通過掃描 META-INF/services 目錄下的文件找到實現(xiàn)類的全限定名,把類加載值 JVM, 并實例化它;

SPI 的實現(xiàn)類必須攜帶一個不帶參數(shù)的構造方法。

示例:

a59d2dfa-c8f0-11ed-bfe3-dac502259ad0.jpg

spi-interface 模塊定義

定義一組接口:public interface MyDriver 

spi-jd-driver

spi-ali-driver

實現(xiàn)為:public class JdDriver implements MyDriver
  public class AliDriver implements MyDriver 

在 src/main/resources/ 下建立 /META-INF/services 目錄, 新增一個以接口命名的文件 (org.MyDriver 文件)

內(nèi)容是要應用的實現(xiàn)類分別 com.jd.JdDriver 和 com.ali.AliDriver

a5c11152-c8f0-11ed-bfe3-dac502259ad0.png

spi-core

一般都是平臺提供的核心包,包含加載使用實現(xiàn)類的策略等等,我們這邊就簡單實現(xiàn)一下邏輯:a. 沒有找到具體實現(xiàn)拋出異常 b. 如果發(fā)現(xiàn)多個實現(xiàn),分別打印

public void invoker(){
    ServiceLoader  serviceLoader = ServiceLoader.load(MyDriver.class);
    Iterator drivers = serviceLoader.iterator();
    boolean isNotFound = true;
    while (drivers.hasNext()){
        isNotFound = false;
        drivers.next().load();
    }
    if(isNotFound){
        throw new RuntimeException("一個驅動實現(xiàn)類都不存在");
    }
}

spi-test

public class App 
{
    public static void main( String[] args )
    {
        DriverFactory factory = new DriverFactory();
        factory.invoker();
    }
}

1. 引入 spi-core 包,執(zhí)行結果

a5eee73a-c8f0-11ed-bfe3-dac502259ad0.png

2. 引入 spi-core,spi-jd-driver 包

a619b640-c8f0-11ed-bfe3-dac502259ad0.jpg

3. 引入 spi-core,spi-jd-driver,spi-ali-driver

a64478da-c8f0-11ed-bfe3-dac502259ad0.jpg

4. 原理解析

看看我們剛剛是怎么拿到具體的實現(xiàn)類的? 就兩行代碼:

ServiceLoader  serviceLoader = ServiceLoader.load(MyDriver.class);
Iterator drivers = serviceLoader.iterator();

所以,首先我們看 ServiceLoader 類:

public final class ServiceLoader implements Iterable{
//配置文件的路徑
 private static final String PREFIX = "META-INF/services/";
    // 代表被加載的類或者接口
    private final Class service;
    // 用于定位,加載和實例化providers的類加載器
    private final ClassLoader loader;
    // 創(chuàng)建ServiceLoader時采用的訪問控制上下文
    private final AccessControlContext acc;
    // 緩存providers,按實例化的順序排列
    private LinkedHashMap providers = new LinkedHashMap<>();
    // 懶查找迭代器,真正加載服務的類
    private LazyIterator lookupIterator;
  
 //服務提供者查找的迭代器
    private class LazyIterator
        implements Iterator
    {
 .....
private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
//全限定名:com.xxxx.xxx
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }


        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class c = null;
            try {
//通過反射獲取
                c = Class.forName(cn, false, loader);
            }
            if (!service.isAssignableFrom(c)) {
                fail(service, "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            }
        }
........

大概的流程就是下面這張圖:

a666b756-c8f0-11ed-bfe3-dac502259ad0.png

應用程序調(diào)用 ServiceLoader.load 方法

應用程序通過迭代器獲取對象實例,會先判斷 providers 對象中是否已經(jīng)有緩存的示例對象,如果存在直接返回

如果沒有存在,執(zhí)行類轉載讀取 META-INF/services 下的配置文件,獲取所有能被實例化的類的名稱,可以跨越 JAR 獲取配置文件通過反射方法 Class.forName () 加載對象并用 Instance () 方法示例化類將實例化類緩存至 providers 對象中,同步返回。

5. 總結

優(yōu)點:解耦

SPI 的使用,使得第三方服務模塊的裝配控制邏輯與調(diào)用者的業(yè)務代碼分離,不會耦合在一起,應用程序可以根據(jù)實際業(yè)務情況來啟用框架擴展和替換框架組件。

SPI 的使用,使得無須通過下面幾種方式獲取實現(xiàn)類

代碼硬編碼 import 導入

指定類全限定名反射獲取,例如 JDBC4.0 之前;Class.forName("com.mysql.jdbc.Driver")

缺點:

雖然 ServiceLoader 也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現(xiàn)類全部加載并實例化一遍。如果你并不想用某些實現(xiàn)類,它也被加載并實例化了,這就造成了浪費。獲取某個實現(xiàn)類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據(jù)某個參數(shù)來獲取對應的實現(xiàn)類。

6. 對比

a6964dc2-c8f0-11ed-bfe3-dac502259ad0.png







審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • JAVA
    +關注

    關注

    19

    文章

    2981

    瀏覽量

    105850
  • SPI
    SPI
    +關注

    關注

    17

    文章

    1731

    瀏覽量

    93054
  • JDBC
    +關注

    關注

    0

    文章

    25

    瀏覽量

    13478

原文標題:可插拔組件設計機制 —SPI

文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關注!文章轉載請注明出處。

收藏 0人收藏

    評論

    相關推薦

    Java的SPI機制詳解

    作者:京東物流 楊葦葦 1.SPI簡介 SPI(Service Provicer Interface)是Java語言提供的一種接口發(fā)現(xiàn)機制,用來實現(xiàn)接口和接口實現(xiàn)的解耦。簡單來說,就是系統(tǒng)只需要定義
    的頭像 發(fā)表于 03-05 11:35 ?428次閱讀
    Java的<b class='flag-5'>SPI</b><b class='flag-5'>機制</b>詳解

    TE Connectivity推出插拔式 I/O 電纜組件

    全球連接與傳感領域領軍企業(yè)TE Connectivity (TE)最新推出的高速插拔式 I/O 銅質電纜組件支持最新的高速標準,設計速率為 56 Gbps 及更高。
    發(fā)表于 03-14 16:54 ?1144次閱讀

    TE推出插拔式 I O 電纜組件產(chǎn)品介紹-赫聯(lián)電子

    100G 以太網(wǎng)和 InfiniBand 增強型數(shù)據(jù)速率(EDR) 要求。TE提供了自定義布線解決方案及相應的插拔 I/O 殼體和連接器。   TE 已擴展其 QSFP+ 電纜組件產(chǎn)品系列,現(xiàn)包括可解決
    發(fā)表于 05-13 14:47

    聊聊Dubbo - Dubbo擴展機制實戰(zhàn)

    摘要: 在Dubbo的官網(wǎng)上,Dubbo描述自己是一個高性能的RPC框架。今天我想聊聊Dubbo的另一個很棒的特性, 就是它的擴展性。1. Dubbo的擴展機制在Dubbo的官網(wǎng)上,Dubbo描述
    發(fā)表于 06-04 17:33

    空間受限應用中的PMBus熱插拔電路基礎介紹

      摘要:本文詳細介紹了熱插拔電路基礎,以及要求使用系統(tǒng)保護與管理(SPM)和印刷電路板(PCB)基板面極其珍貴的情況下系統(tǒng)設計人員所面臨的諸多挑戰(zhàn)。以模塊化實現(xiàn)利用集成數(shù)字熱插拔控制器時,我們?yōu)槟?/div>
    發(fā)表于 09-26 17:32

    XML在重構制造執(zhí)行系統(tǒng)組件管理中的應用

    基于組件技術研究和開發(fā)重構制造執(zhí)行系統(tǒng)(RMES),需要解決大量不同功能業(yè)務組件的維護問題。本文基于XML-Schema 機制設計了一套標準化描述方法,來定義RMES
    發(fā)表于 08-24 11:31 ?7次下載

    插拔式中間繼電器的分類

    插拔式中間繼電器的分類
    發(fā)表于 11-28 11:42 ?16次下載

    嵌入式Linux下插拔輸入驅動機制研究

    本文介紹了嵌入式linux下的輸入驅動接口,詳細分析了輸入驅動中如何實現(xiàn)插拔機制,描述了USB人機接口設備和
    發(fā)表于 07-27 15:38 ?16次下載

    Zelio Relay插拔式中間繼電器的介紹及其各型號的介紹

    電子發(fā)燒友網(wǎng)站提供《Zelio Relay插拔式中間繼電器的介紹及其各型號的介紹.pdf》資料免費下載
    發(fā)表于 09-21 09:11 ?5次下載

    基于SPI協(xié)議的SD卡讀寫機制與實現(xiàn)方法

    基于SPI協(xié)議的SD卡讀寫機制與實現(xiàn)方法。
    發(fā)表于 03-25 11:21 ?27次下載
    基于<b class='flag-5'>SPI</b>協(xié)議的SD卡讀寫<b class='flag-5'>機制</b>與實現(xiàn)方法

    JDK內(nèi)置的一種服務SPI機制

    SPI(Service Provider Interface)是JDK內(nèi)置的一種服務提供發(fā)現(xiàn)機制,可以用來啟用框架擴展和替換組件,主要用于框架中開發(fā),例如Dubbo、Spring
    的頭像 發(fā)表于 02-15 09:15 ?895次閱讀

    基于spring的SPI擴展機制是如何實現(xiàn)的?

    基本上,你一說是基于 spring 的 SPI 擴展機制,再把spring.factories文件和EnableAutoConfiguration提一下,那么這個問題就答的八九不離十了。
    的頭像 發(fā)表于 03-07 09:17 ?1165次閱讀

    Java、Spring、Dubbo三者SPI機制的原理和區(qū)別

    其實我之前寫過一篇類似的文章,但是這篇文章主要是剖析dubbo的SPI機制的源碼,中間只是簡單地介紹了一下Java、Spring的SPI機制
    的頭像 發(fā)表于 06-05 15:21 ?1206次閱讀
    Java、Spring、Dubbo三者<b class='flag-5'>SPI</b><b class='flag-5'>機制</b>的原理和區(qū)別

    什么是SPI機制

    的ContextClassLoader加載以便使用)。本次將對 SPI機制進行詳解,并結合案例介紹其在實際場景中具體使用。 2、什么是SPI機制
    的頭像 發(fā)表于 10-08 15:03 ?1334次閱讀
    什么是<b class='flag-5'>SPI</b><b class='flag-5'>機制</b>

    PCIe熱插拔機制介紹

    前言本文主要講述PCIe熱插拔機制,通過圖形方式方便讀者快速掌握。 一、概述 如果在PCIe設備不支持熱插拔的條件下,在不斷電的情況下插拔一塊PCIe SSD時,很可能會對主板或PCI
    的頭像 發(fā)表于 11-20 09:07 ?1393次閱讀
    PCIe熱<b class='flag-5'>插拔</b><b class='flag-5'>機制</b><b class='flag-5'>介紹</b>

    電子發(fā)燒友

    中國電子工程師最喜歡的網(wǎng)站

    • 2931785位工程師會員交流學習
    • 獲取您個性化的科技前沿技術信息
    • 參加活動獲取豐厚的禮品