0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

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

可插拔組件設(shè)計(jì)機(jī)制—SPI介紹

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

1.SPI 是什么?

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

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

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

2. 使用場(chǎng)景

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

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

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

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

3. 使用介紹

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

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

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

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

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

示例:

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

spi-interface 模塊定義

定義一組接口:public interface MyDriver 

spi-jd-driver

spi-ali-driver

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

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

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

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

spi-core

一般都是平臺(tái)提供的核心包,包含加載使用實(shí)現(xiàn)類的策略等等,我們這邊就簡(jiǎn)單實(shí)現(xiàn)一下邏輯:a. 沒(méi)有找到具體實(shí)現(xiàn)拋出異常 b. 如果發(fā)現(xiàn)多個(gè)實(shí)現(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("一個(gè)驅(qū)動(dòng)實(shí)現(xiàn)類都不存在");
    }
}

spi-test

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

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

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. 原理解析

看看我們剛剛是怎么拿到具體的實(shí)現(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;
    // 用于定位,加載和實(shí)例化providers的類加載器
    private final ClassLoader loader;
    // 創(chuàng)建ServiceLoader時(shí)采用的訪問(wèn)控制上下文
    private final AccessControlContext acc;
    // 緩存providers,按實(shí)例化的順序排列
    private LinkedHashMap providers = new LinkedHashMap<>();
    // 懶查找迭代器,真正加載服務(wù)的類
    private LazyIterator lookupIterator;
  
 //服務(wù)提供者查找的迭代器
    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 {
//通過(guò)反射獲取
                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

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

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

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

5. 總結(jié)

優(yōu)點(diǎn):解耦

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

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

代碼硬編碼 import 導(dǎo)入

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

缺點(diǎn):

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

6. 對(duì)比

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







審核編輯:劉清

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

    關(guān)注

    19

    文章

    2948

    瀏覽量

    104385
  • SPI
    SPI
    +關(guān)注

    關(guān)注

    17

    文章

    1685

    瀏覽量

    91084
  • JDBC
    +關(guān)注

    關(guān)注

    0

    文章

    25

    瀏覽量

    13382

原文標(biāo)題:可插拔組件設(shè)計(jì)機(jī)制 —SPI

文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

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

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

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

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

    聊聊Dubbo - Dubbo擴(kuò)展機(jī)制實(shí)戰(zhàn)

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

    空間受限應(yīng)用中的PMBus熱插拔電路基礎(chǔ)介紹

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

    介紹AUTOSAR支持的四種功能安全機(jī)制

    1、AUTOSAR的四種功能安全機(jī)制雖然AUTOSAR不是一個(gè)完整的安全解決方案,但它提供了一些安全機(jī)制用于支持安全關(guān)鍵系統(tǒng)的開(kāi)發(fā)。本文用于介紹AUTOSAR支持的四種功能安全機(jī)制:內(nèi)
    發(fā)表于 06-10 17:33

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

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

    插拔式中間繼電器的分類

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

    嵌入式Linux下插拔輸入驅(qū)動(dòng)機(jī)制研究

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

    SPI模式下MMC卡的讀寫機(jī)制

    SPI模式下MMC卡的讀寫機(jī)制  多媒體卡MMC(MultiMedia Card)是由美國(guó)SanDisk公司和德國(guó)Simens公司于1997年共同開(kāi)發(fā)的一種多功能Flash存儲(chǔ)設(shè)備?;贏RM7芯
    發(fā)表于 03-29 15:13 ?1165次閱讀
    <b class='flag-5'>SPI</b>模式下MMC卡的讀寫<b class='flag-5'>機(jī)制</b>

    Zelio Relay插拔式中間繼電器的介紹及其各型號(hào)的介紹

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

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

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

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

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

    基于spring的SPI擴(kuò)展機(jī)制是如何實(shí)現(xiàn)的?

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

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

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

    什么是SPI機(jī)制

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