欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Dubbo之SPI機(jī)制的實(shí)現(xiàn)原理和優(yōu)勢(shì)分析

 更新時(shí)間:2025年05月21日 16:41:38   作者:小馬不敲代碼  
這篇文章主要介紹了Dubbo之SPI機(jī)制的實(shí)現(xiàn)原理和優(yōu)勢(shì),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

Dubbo中SPI機(jī)制的實(shí)現(xiàn)原理和優(yōu)勢(shì)

確保系統(tǒng)的擴(kuò)展性是我們開(kāi)展架構(gòu)設(shè)計(jì)工作的核心目標(biāo)之一。實(shí)現(xiàn)擴(kuò)展性的方法有很多,JDK 本身內(nèi)置了一個(gè) SPI(Service Provider Interface,服務(wù)提供者接口)機(jī)制,來(lái)幫開(kāi)發(fā)人員動(dòng)態(tài)加載各種不同的實(shí)現(xiàn)類,只要這些實(shí)現(xiàn)類遵循一定的開(kāi)發(fā)規(guī)范即可。

另一方面,JDK 自帶的 SPI 機(jī)制存在一定的缺陷,因此市面上有些框架對(duì) JDK 中的 SPI 機(jī)制做了一些增強(qiáng),這方面的代表性框架就是 Dubbo。

JDK 中的 SPI 機(jī)制解析

如果我們采用 JDK 中的 SPI,具體的開(kāi)發(fā)工作會(huì)涉及三個(gè)步驟。

在這里插入圖片描述

對(duì)于 SPI 的開(kāi)發(fā)者而言,我們需要設(shè)計(jì)一個(gè)服務(wù)接口,然后根據(jù)業(yè)務(wù)場(chǎng)景提供不同的實(shí)現(xiàn)類,這是第一步。

接下來(lái)的第二步是關(guān)鍵,我們需要?jiǎng)?chuàng)建一個(gè)以服務(wù)接口命名的配置文件,并把這個(gè)文件放置到代碼工程的 META-INF/services 目錄下。請(qǐng)注意,在這個(gè)配置文件中,我們需要指定服務(wù)接口對(duì)應(yīng)實(shí)現(xiàn)類的完整類名。通過(guò)這一步,我們可以得到了一個(gè)包含 SPI 類和配置的 jar 包。

最后,SPI 的使用者就可以加載這個(gè) jar 包并找到其中的這個(gè)配置文件,并根據(jù)所配置的實(shí)現(xiàn)類完整類名對(duì)這些類進(jìn)行實(shí)例化。

上圖中的后面兩個(gè)步驟實(shí)際上都是為了遵循 JDK 中 SPI 的實(shí)現(xiàn)機(jī)制而進(jìn)行的配置工作。

為了實(shí)現(xiàn)對(duì) SPI 實(shí)現(xiàn)類的動(dòng)態(tài)記載,JDK 專門(mén)提供了一個(gè) ServiceLoader 工具類,這個(gè)工具類的使用方法如下所示:

public static void main(String[] args) {
  ServiceLoader<LogProvider> loader = ServiceLoader.load(LogProvider.class);
  for (LogProvider provider : loader) {
   System.out.println(provider.getClass());
   provider.info(“testInfo”);
  }
}

這里有一個(gè) LogProvider 接口,并通過(guò) ServiceLoader 的 load 方法將這個(gè)接口所配置的實(shí)現(xiàn)類加載到內(nèi)存中,從而可以方便地使用這些 SPI 實(shí)現(xiàn)類所提供的功能。

接下來(lái),讓我們來(lái)分析一下這個(gè) ServiceLoader 工具類的實(shí)現(xiàn)原理。

ServiceLoader 本身實(shí)現(xiàn)了 JDK 中的 Iterable 接口,因此在上面的代碼示例中,通過(guò) ServiceLoader.load 方法我們獲取的是一個(gè)迭代器,而底層則用到了 ServiceLoader.LazyIterator 這個(gè)迭代器類。

從命名上看,LazyIterator 是一個(gè)具備延遲加載機(jī)制的迭代器,它有 hasNextService 和 nexServicet 這兩個(gè)核心方法。我們先來(lái)看 hasNextService 方法:

//配置文件路徑
static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        // 通過(guò) PREFIX 前綴與服務(wù)接口的名稱,我們可以找到目標(biāo) SPI 配置文件
        String fullName = PREFIX + service.getName();
        // 加載配置文件
        if (loader == null)
            configs = ClassLoader.getSystemResources(fullName);
        else
            configs = loader.getResources(fullName);
    }
    // 對(duì) SPI 配置文件進(jìn)行遍歷,并解析配置內(nèi)容
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        // 解析配置文件
        pending = parse(service, configs.nextElement());
 }
 // 更新 nextName 字段
    nextName = pending.next();
    return true;
}

可以看到,hasNextService 方法的核心作用是找到并解析配置文件。而接下來(lái)要展開(kāi)的 nextService 方法則負(fù)責(zé)對(duì)所配置的類進(jìn)行實(shí)例化,核心實(shí)現(xiàn)如下所示:

private S nextService() {
    String cn = nextName;
    nextName = null;
    // 加載 nextName 字段指定的類
 Class<?> c = Class.forName(cn, false, loader);
 // 檢測(cè)類型
    if (!service.isAssignableFrom(c)) {
        fail(service, "Provider " + cn  + " not a subtype");
 }
 // 創(chuàng)建實(shí)現(xiàn)類的對(duì)象
 S p = service.cast(c.newInstance());
 // 緩存已創(chuàng)建的對(duì)象
 providers.put(cn, p);
    return p;
}

這里通過(guò) newInstance 方法創(chuàng)建了目標(biāo)實(shí)例,并將已創(chuàng)建的實(shí)例對(duì)象放到 providers 集合中進(jìn)行緩存,從而提高訪問(wèn)效率。

Dubbo 中的 SPI 機(jī)制解析

為了實(shí)現(xiàn)框架自身的擴(kuò)展性,Dubbo 也采用了類似 JDK 中 SPI 的設(shè)計(jì)思想,但提供了一套新的實(shí)現(xiàn)方式,并添加了一些擴(kuò)展功能。

Dubbo 中與 SPI 機(jī)制相關(guān)的注解主要包括@SPI、@Adaptive 和@Activate,其中@SPI 注解提供了與 JDK 中 SPI 類似的功能。

在這里插入圖片描述

這三個(gè)注解的應(yīng)用場(chǎng)景各不相同,其中@SPI 注解為 Dubbo 提供了最基礎(chǔ)的 SPI 機(jī)制,而@Adaptive 和@Activate 注解都是構(gòu)建在這個(gè)注解之上,因此我們重點(diǎn)介紹@SPI 注解。如果在某個(gè)接口上添加了這個(gè)注解,那么 Dubbo 在運(yùn)行過(guò)程中就會(huì)去查找接口對(duì)應(yīng)的擴(kuò)展點(diǎn)實(shí)現(xiàn)。

在 Dubbo 中,隨處可以看到@SPI 注解的應(yīng)用場(chǎng)景。

舉個(gè)例子,Protocol 接口定義如下:

@SPI("dubbo")public interface Protocol

可以看到,在這個(gè)接口上使用的就是@SPI(“dubbo”) 注解。

請(qǐng)注意,在@SPI 注解中可以指定默認(rèn)擴(kuò)展點(diǎn)的名稱,例如這里的“dubbo”用來(lái)表明在 Protocol 接口的所有實(shí)現(xiàn)類中,DubboProtocol 是它的默認(rèn)實(shí)現(xiàn)。

有了 SPI 的定義,我們接下來(lái)看一看 Dubbo 中 SPI 配置信息的存儲(chǔ)方式。我們已經(jīng)知道,JDK 只會(huì)把 SPI 配置存放在 META-INF/services/這個(gè)目錄下,而 Dubbo 則提供了三個(gè)類似這樣的目錄:

在這里插入圖片描述

作為示例,我們繼續(xù)圍繞上面提到的 Protocol 接口展開(kāi)討論。

針對(duì) Protocol 接口,Dubbo 提供了 gRPCProtocol、DubboProtocol 等多個(gè)實(shí)現(xiàn)類,并通過(guò) SPI 機(jī)制完成對(duì)具體某種實(shí)現(xiàn)方案的加載過(guò)程。

讓我們分別來(lái)到提供這些實(shí)現(xiàn)類的代碼工程 dubbo-rpc-grpc 和 dubbo-rpc-dubbo,會(huì)發(fā)現(xiàn)在 META-INF/dubbo/internal/目錄下都包含了一個(gè) com.apache.dubbo.rpc.Protocol 配置文件。其中,dubbo-rpc-grpc 工程的代碼結(jié)構(gòu)如圖所示:

在這里插入圖片描述

類似的,dubbo-rpc-dubbo 工程的代碼結(jié)構(gòu)如下圖所示:

在這里插入圖片描述

我們分別打開(kāi)這兩個(gè)工程的 com.apache.dubbo.rpc.Protocol 配置文件,可以發(fā)現(xiàn)它們分別指向了 org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol 和 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 類。

  • //dubbo-rpc-grpc 工程:
grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol
  • //dubbo-rpc-dubbo 工程:
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

當(dāng) Dubbo 在引用具體某一個(gè)代碼工程時(shí),就可以通過(guò)這個(gè)工程中的配置項(xiàng)就可以找到 Dubbo 接口對(duì)應(yīng)的擴(kuò)展點(diǎn)實(shí)現(xiàn)。

同時(shí),我們從上面配置項(xiàng)中也可以看出,Dubbo 中采用的定義方式與 JDK 中的不一樣。Dubbo 使用的一個(gè) Key 值(如上面的 gRPC 和 Dubbo)來(lái)指定具體的配置項(xiàng)名稱,而不是采用完整類路徑。

介紹完@SPI 注解,我們接下來(lái)看 Dubbo 中的 ExtensionLoader 類,這個(gè)類扮演著與 JDK 中 ServiceLoader 工具類相同的角色。

ExtensionLoader 是實(shí)現(xiàn)擴(kuò)展點(diǎn)加載的核心類,如果我們想要獲取 DubboProtocol 這個(gè)實(shí)現(xiàn)類,那么可以采用以下方式:

DubboProtocol dubboProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME);

我們來(lái)看一下這里 getExtension 方法的細(xì)節(jié),這個(gè)方法代碼如下所示:

public T getExtension(String name) {
     ...
     //從緩存中獲取目標(biāo)對(duì)象
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
         //將目標(biāo)對(duì)象放到緩存中
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                 //創(chuàng)建目標(biāo)對(duì)象
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
}

我們看到這里同樣用到了緩存機(jī)制。這個(gè)方法會(huì)首先檢查緩存中是否已經(jīng)存在擴(kuò)展點(diǎn)實(shí)例,如果沒(méi)有則通過(guò) createExtension 方法進(jìn)行創(chuàng)建。

我們一路跟蹤 createExtension 方法,終于看到了熟悉的 SPI 機(jī)制,如下所示:

private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
             //確定緩存名稱
        }
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
     //分別從三個(gè)目錄中加載類實(shí)例
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
}

在這里,我們調(diào)用了三次 loadFile 方法,分別在 META-INF/dubbo/、META-INF/services/和 META-INF/dubbo/internal/這三個(gè)目錄中加載擴(kuò)展點(diǎn)。在 loadFile 方法中,Dubbo 是直接通過(guò) Class.forName 的形式加載這些 SPI 的擴(kuò)展類,并進(jìn)行緩存。

到這里,我們發(fā)現(xiàn),為了提升實(shí)例類的加載速度,Dubbo 和 JDK 都采用了緩存機(jī)制,這是它們的一個(gè)共同點(diǎn)。但實(shí)際上,我們也已經(jīng)可以梳理 Dubbo 中 SPI 機(jī)制與 JDK 中 SPI 機(jī)制的區(qū)別,核心有兩點(diǎn),就是 配置文件位置和 獲取實(shí)現(xiàn)類的條件。

在這里插入圖片描述

從加載 SPI 實(shí)例的配置文件位置來(lái)看,Dubbo 支持更多的加載路徑。JDK 只能加載一個(gè)固定的 META-INF/services,而 Dubbo 有三個(gè)路徑。

就獲取實(shí)現(xiàn)類的條件而言,Dubbo 采用的是直接通過(guò)名稱對(duì)應(yīng)的 Key 值來(lái)定位具體實(shí)現(xiàn)類,而 ServiceLoader 內(nèi)部使用的是一個(gè)迭代器,在獲取目標(biāo)接口的實(shí)現(xiàn)類時(shí),只能通過(guò)遍歷的方式把配置文件中的類全部加載并實(shí)例化,顯然這樣效率比較低下。

簡(jiǎn)單來(lái)說(shuō),Dubbo 沒(méi)有直接沿用 JDK SPI 機(jī)制,而是自己實(shí)現(xiàn)一套的主要目的就是克服這種效率低下的情況,并提供了更多的靈活性。

總結(jié)

從 Dubbo 配置項(xiàng)的定義中發(fā)現(xiàn),Dubbo 采用了與 JDK 不同的實(shí)現(xiàn)機(jī)制。雖然 Dubbo 也采用了 SPI 機(jī)制,也是從 jar 包中動(dòng)態(tài)加載實(shí)現(xiàn)類,但它的實(shí)現(xiàn)方式與 JDK 中基于 ServiceLoader 是不一樣的。于是,詳細(xì)分析了 JDK 和 Dubbo 在 SPI 機(jī)制設(shè)計(jì)和實(shí)現(xiàn)上的差異,并闡明了 Dubbo 內(nèi)部的實(shí)現(xiàn)原理和所具備的優(yōu)勢(shì)。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java利用openoffice將doc、docx轉(zhuǎn)為pdf實(shí)例代碼

    Java利用openoffice將doc、docx轉(zhuǎn)為pdf實(shí)例代碼

    這篇文章主要介紹了Java利用openoffice將doc、docx轉(zhuǎn)為pdf實(shí)例代碼,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • 淺談Java日志框架slf4j作用及其實(shí)現(xiàn)原理

    淺談Java日志框架slf4j作用及其實(shí)現(xiàn)原理

    日志記錄是應(yīng)用程序運(yùn)行中必不可少的一部分。這篇文章主要介紹了淺談Java日志框架slf4j作用及其實(shí)現(xiàn)原理,SLF4J是一個(gè)日志框架抽象層,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • java實(shí)現(xiàn)俄羅斯方塊小游戲

    java實(shí)現(xiàn)俄羅斯方塊小游戲

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)俄羅斯方塊小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • Java8并發(fā)新特性CompletableFuture

    Java8并發(fā)新特性CompletableFuture

    這篇文章主要介紹了Java8并發(fā)新特性CompletableFuture,CompletableFuture針對(duì)Future接口做了改進(jìn),相比Callable/Runnable接口它支持多任務(wù)進(jìn)行鏈?zhǔn)秸{(diào)用、組合、多任務(wù)并發(fā)處理,下面文章更多相關(guān)內(nèi)容得介紹,需要的小伙伴可以參考一下
    2022-06-06
  • 簡(jiǎn)述springboot及springboot cloud環(huán)境搭建

    簡(jiǎn)述springboot及springboot cloud環(huán)境搭建

    這篇文章主要介紹了簡(jiǎn)述springboot及springboot cloud環(huán)境搭建的方法,包括spring boot 基礎(chǔ)應(yīng)用環(huán)境搭建,需要的朋友可以參考下
    2017-07-07
  • 解決IDEA報(bào)錯(cuò):編碼GBK的不可映射字符問(wèn)題

    解決IDEA報(bào)錯(cuò):編碼GBK的不可映射字符問(wèn)題

    這篇文章主要介紹了解決IDEA報(bào)錯(cuò):編碼GBK的不可映射字符問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • SpringBoot+SpringSecurity實(shí)現(xiàn)基于真實(shí)數(shù)據(jù)的授權(quán)認(rèn)證

    SpringBoot+SpringSecurity實(shí)現(xiàn)基于真實(shí)數(shù)據(jù)的授權(quán)認(rèn)證

    Spring Security是一個(gè)功能強(qiáng)大且高度可定制的身份驗(yàn)證和訪問(wèn)控制框架,Spring Security主要做兩個(gè)事情,認(rèn)證、授權(quán)。這篇文章主要介紹了SpringBoot+SpringSecurity實(shí)現(xiàn)基于真實(shí)數(shù)據(jù)的授權(quán)認(rèn)證,需要的朋友可以參考下
    2021-05-05
  • SpringBoot整合JWT Token的完整步驟

    SpringBoot整合JWT Token的完整步驟

    JSON Web Token是目前最流行的跨域認(rèn)證解決方案,適合前后端分離項(xiàng)目通過(guò)Restful API進(jìn)行數(shù)據(jù)交互時(shí)進(jìn)行身份認(rèn)證,這篇文章主要給大家介紹了關(guān)于SpringBoot整合JWT Token的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • Hibernate中Session.get()方法和load()方法的詳細(xì)比較

    Hibernate中Session.get()方法和load()方法的詳細(xì)比較

    今天小編就為大家分享一篇關(guān)于Hibernate中Session.get()方法和load()方法的詳細(xì)比較,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-03-03
  • Java基礎(chǔ)之簡(jiǎn)單介紹一下Maven

    Java基礎(chǔ)之簡(jiǎn)單介紹一下Maven

    今天給大家復(fù)習(xí)一下Java基礎(chǔ)知識(shí),簡(jiǎn)單介紹Maven,文中有非常詳細(xì)的解釋,對(duì)Java初學(xué)者很有幫助喲,需要的朋友可以參考下
    2021-05-05

最新評(píng)論