真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

DubboSPI的特性及實(shí)現(xiàn)原理是什么

這篇文章主要介紹“Dubbo SPI的特性及實(shí)現(xiàn)原理是什么”,在日常操作中,相信很多人在Dubbo SPI的特性及實(shí)現(xiàn)原理是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Dubbo SPI的特性及實(shí)現(xiàn)原理是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

創(chuàng)新互聯(lián)公司是一家以成都網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計、品牌設(shè)計、軟件運(yùn)維、網(wǎng)站推廣、小程序App開發(fā)等移動開發(fā)為一體互聯(lián)網(wǎng)公司。已累計為LED顯示屏等眾行業(yè)中小客戶提供優(yōu)質(zhì)的互聯(lián)網(wǎng)建站和軟件開發(fā)服務(wù)。

一、概述

SPI 全稱為 Service Provider Interface,是一種模塊間組件相互引用的機(jī)制。其方案通常是提供方將接口實(shí)現(xiàn)類的全名配置在classPath下的指定文件中,由調(diào)用方讀取并加載。這樣需要替換某個組件時,只需要引入新的JAR包并在其中包含新的實(shí)現(xiàn)類和配置文件即可,調(diào)用方的代碼無需任何調(diào)整。優(yōu)秀的SPI框架能夠提供單接口多實(shí)現(xiàn)類時的優(yōu)先級選擇,由用戶指定選擇哪個實(shí)現(xiàn)。

得益于這些能力,SPI對模塊間的可插拔機(jī)制和動態(tài)擴(kuò)展提供了非常好的支撐。

本文將簡單介紹JDK自帶的SPI,分析SPI和雙親委派的關(guān)系,進(jìn)而重點(diǎn)分析DUBBO的SPI機(jī)制;比較兩者有何不同,DUBBO的SPI帶來了哪些額外的能力。

二、JDK自帶SPI

提供者在classPath或者jar包的META-INF/services/目錄創(chuàng)建以服務(wù)接口命名的文件,調(diào)用者通過java.util.ServiceLoader加載文件內(nèi)容中指定的實(shí)現(xiàn)類。

1. 代碼示例

  • 首先定義一個接口Search

search示例接口

package com.example.studydemo.spi;
public interface Search {
    void search();
}
  • 實(shí)現(xiàn)類FileSearchImpl實(shí)現(xiàn)該接口

文件搜索實(shí)現(xiàn)類

package com.example.studydemo.spi;
public class FileSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("文件搜索");
    }
}
  • 實(shí)現(xiàn)類DataBaseSearchImpl實(shí)現(xiàn)該接口

數(shù)據(jù)庫搜索實(shí)現(xiàn)類

package com.example.studydemo.spi;
public class DataBaseSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("數(shù)據(jù)庫搜索");
    }
}
  • 在項(xiàng)目的META-INF/services文件夾下,創(chuàng)建Search文件

Dubbo SPI的特性及實(shí)現(xiàn)原理是什么

文件內(nèi)容為:

com.example.studydemo.spi.DataBaseSearchImpl
com.example.studydemo.spi.FileSearchImpl

測試:

import java.util.ServiceLoader;
public class JavaSpiTest {
    public static void main(String[] args) {
        ServiceLoader searches = ServiceLoader.load(Search.class);
        searches.forEach(Search::search);
    }
}

結(jié)果為:

Dubbo SPI的特性及實(shí)現(xiàn)原理是什么

2. 簡單分析

ServiceLoader作為JDK提供的一個服務(wù)實(shí)現(xiàn)查找工具類,調(diào)用自身load方法加載Search接口的所有實(shí)現(xiàn)類,然后可以使用for循環(huán)遍歷實(shí)現(xiàn)類進(jìn)行方法調(diào)用。

有一個疑問:META-INF/services/目錄是硬編碼的嗎,其它路徑行不行?答案是不行。

跟進(jìn)到ServiceLoader類中,第一行代碼就是private static final String PREFIX = “META-INF/services/”,所以SPI配置文件只能放在classPath或者jar包的這個指定目錄下面。

ServiceLoader的文件載入路徑

public final class ServiceLoader
    implements Iterable
{
    //硬編碼寫死了文件路徑
    private static final String PREFIX = "META-INF/services/";
 
    // The class or interface representing the service being loaded
    private final Class service;
 
    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

JDK SPI的使用比較簡單,做到了基本的加載擴(kuò)展組件的功能,但有以下幾點(diǎn)不足:

  • 需要遍歷所有的實(shí)現(xiàn)并實(shí)例化,想要找到某一個實(shí)現(xiàn)只能循環(huán)遍歷,一個一個匹配;

  • 配置文件中只是簡單的列出了所有的擴(kuò)展實(shí)現(xiàn),而沒有給他們命名,導(dǎo)致在程序中很難去準(zhǔn)確的引用它們;

  • 擴(kuò)展之間彼此存在依賴,做不到自動注入和裝配,不提供上下文內(nèi)的IOC和AOP功能;

  • 擴(kuò)展很難和其他的容器框架集成,比如擴(kuò)展依賴了一個外部spring容器中的bean,原生的JDK SPI并不支持。

三、SPI與雙親委派

1. SPI加載到何處

基于類加載的雙親委派原則,由JDK內(nèi)部加載的class默認(rèn)應(yīng)該歸屬于bootstrap類加載器,那么SPI機(jī)制加載的class是否也屬于bootstrap呢 ?

答案是否定的,原生SPI機(jī)制通過ServiceLoader.load方法由外部指定類加載器,或者默認(rèn)取Thread.currentThread().getContextClassLoader()線程上下文的類加載器,從而避免了class被載入bootstrap加載器。

2.SPI是否破壞了雙親委派

雙親委派的本質(zhì)涵義是在rt.jar包和外部class之間建立一道classLoader的鴻溝,即rt.jar內(nèi)的class不應(yīng)由外部classLoader加載,外部class不應(yīng)由bootstrap加載。

SPI僅是提供了一種在JDK代碼內(nèi)部干預(yù)外部class文件加載的機(jī)制,并未強(qiáng)制指定加載到何處;外部的class還是由外部的classLoader加載,未跨越這道鴻溝,也就談不上破壞雙親委派。

原生ServiceLoader的類加載器

//指定類加載器
public static  ServiceLoader load(Class service,ClassLoader loader)
//默認(rèn)取前線程上下文的類加載器
public static  ServiceLoader load(Class service)

四、Dubbo SPI

Dubbo借鑒了Java SPI的思想,與JDK的ServiceLoader相對應(yīng)的,Dubbo設(shè)計了ExtensionLoader類,其提供的功能比JDK更為強(qiáng)大。

1. 基本概念

首先介紹一些基本概念,讓大家有一個初步的認(rèn)知。

  • 擴(kuò)展點(diǎn)(Extension Point):是一個Java的接口。

  • 擴(kuò)展(Extension):擴(kuò)展點(diǎn)的實(shí)現(xiàn)類

  • 擴(kuò)展實(shí)例(Extension Instance):擴(kuò)展點(diǎn)實(shí)現(xiàn)類的實(shí)例。

  • 自適應(yīng)擴(kuò)展實(shí)例(Extension Adaptive Instance)

自適應(yīng)擴(kuò)展實(shí)例其實(shí)就是一個擴(kuò)展類的代理對象,它實(shí)現(xiàn)了擴(kuò)展點(diǎn)接口。在調(diào)用擴(kuò)展點(diǎn)的接口方法時,會根據(jù)實(shí)際的參數(shù)來決定要使用哪個擴(kuò)展。

比如一個Search的擴(kuò)展點(diǎn),有一個search方法。有兩個實(shí)現(xiàn)FileSearchImpl和DataBaseSearchImpl。Search的自適應(yīng)實(shí)例在調(diào)用接口方法的時候,會根據(jù)search方法中的參數(shù),來決定要調(diào)用哪個Search的實(shí)現(xiàn)。

如果方法參數(shù)中有name=FileSearchImpl,那么就調(diào)用FileSearchImpl的search方法。如果name=DataBaseSearchImpl,就調(diào)用DataBaseSearchImpl的search方法。 自適應(yīng)擴(kuò)展實(shí)例在Dubbo中的使用非常廣泛。

在Dubbo中每一個擴(kuò)展點(diǎn)都可以有自適應(yīng)的實(shí)例,如果我們沒有使用@Adaptive人工指定,Dubbo會使用字節(jié)碼工具自動生成一個。

  • SPI Annotation

    作用于擴(kuò)展點(diǎn)的接口上,表明該接口是一個擴(kuò)展點(diǎn),可以被Dubbo的ExtentionLoader加載

  • Adaptive

@Adaptive注解可以使用在類或方法上。用在方法上表示這是一個自適應(yīng)方法,Dubbo生成自適應(yīng)實(shí)例時會在方法中植入動態(tài)代理的代碼。方法內(nèi)部會根據(jù)方法的參數(shù)來決定使用哪個擴(kuò)展。

@Adaptive注解用在類上代表該實(shí)現(xiàn)類是一個自適應(yīng)類,屬于人為指定的場景,Dubbo就不會為該SPI接口生成代理類,最典型的應(yīng)用如AdaptiveCompiler、AdaptiveExtensionFactory等。

@Adaptive注解的值為字符串?dāng)?shù)組,數(shù)組中的字符串是key值,代碼中要根據(jù)key值來獲取對應(yīng)的Value值,進(jìn)而加載相應(yīng)的extension實(shí)例。比如new String[]{“key1”,”key2”},表示會先在URL中尋找key1的值,

如果找到則使用此值加載extension,如果key1沒有,則尋找key2的值,如果key2也沒有,則使用SPI注解的默認(rèn)值,如果SPI注解沒有默認(rèn)值,則將接口名按照首字母大寫分成多個部分,

然后以’.’分隔,例如org.apache.dubbo.xxx.YyyInvokerWrapper接口名會變成yyy.invoker.wrapper,然后以此名稱做為key到URL尋找,如果仍沒有找到則拋出IllegalStateException異常。

  • ExtensionLoader
    類似于Java SPI的ServiceLoader,負(fù)責(zé)擴(kuò)展的加載和生命周期維護(hù)。ExtensionLoader的作用包括:解析配置文件加載extension類、生成extension實(shí)例并實(shí)現(xiàn)IOC和AOP、創(chuàng)建自適應(yīng)的extension等,下文會重點(diǎn)分析。

  • 擴(kuò)展名
    和Java SPI不同,Dubbo中的擴(kuò)展都有一個名稱,用于在應(yīng)用中引用它們。比如
    registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
    dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

  • 加載路徑
    Java SPI從/META-INF/services目錄加載擴(kuò)展配置,Dubbo從以下路徑去加載擴(kuò)展配置文件:
    META-INF/dubbo/internal
    META-INF/dubbo
    META-INF/services
    其中META-INF/dubbo對開發(fā)者發(fā)放,META-INF/dubbo/internal 這個路徑是用來加載Dubbo內(nèi)部的拓展點(diǎn)的。

2. 代碼示例

定義一個接口,標(biāo)注上dubbo的SPI注解,賦予默認(rèn)值,并提供兩個extension實(shí)現(xiàn)類

package com.example.studydemo.spi;
@SPI("dataBase")
public interface Search {
    void search();
}
public class FileSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("文件搜索");
    }
}
public class DataBaseSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("數(shù)據(jù)庫搜索");
    }
}

在META-INF/dubbo 路徑下創(chuàng)建Search文件

Dubbo SPI的特性及實(shí)現(xiàn)原理是什么

文件內(nèi)容如下:

dataBase=com.example.studydemo.spi.DataBaseSearchImpl
file=com.example.studydemo.spi.FileSearchImpl

編寫測試類進(jìn)行測試,內(nèi)容如下:

public class DubboSpiTest {
    public static void main(String[] args) {
        ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Search.class);
        Search fileSearch = extensionLoader.getExtension("file");
        fileSearch.search();
        Search dataBaseSearch = extensionLoader.getExtension("dataBase");
        dataBaseSearch.search();
        System.out.println(extensionLoader.getDefaultExtensionName());
        Search defaultSearch = extensionLoader.getDefaultExtension();
        defaultSearch.search();
    }
}

結(jié)果為:

Dubbo SPI的特性及實(shí)現(xiàn)原理是什么

從代碼示例上來看,Dubbo SPI與Java SPI在這幾方面是類似的:

  • 接口及相應(yīng)的實(shí)現(xiàn)

  • 配置文件

  • 加載類及加載具體實(shí)現(xiàn)

3源碼分析

下面深入到源碼看看SPI在Dubbo中是怎樣工作的,以Protocol接口為例進(jìn)行分析。

//1、得到Protocol的擴(kuò)展加載對象extensionLoader,由這個加載對象獲得對應(yīng)的自適應(yīng)擴(kuò)展類
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
//2、根據(jù)擴(kuò)展名獲取對應(yīng)的擴(kuò)展類
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");

在獲取擴(kuò)展實(shí)例前要先獲取Protocol接口的ExtensionLoader組件,通過ExtensionLoader來獲取相應(yīng)的Protocol實(shí)例Dubbo實(shí)際是為每個SPI接口都創(chuàng)建了一個對應(yīng)的ExtensionLoader。

ExtensionLoader組件

public static  ExtensionLoader getExtensionLoader(Class type) {
    if (type == null)
        throw new IllegalArgumentException("Extension type == null");
    if(!type.isInterface()) {
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    if(!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    //EXTENSION_LOADERS為ConcurrentMap,存儲Class對應(yīng)的ExtensionLoader
    ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
        loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

EXTENSION_LOADERS是一個 ConcurrentMap,以接口Protocol為key,以ExtensionLoader對象為value;保存的是Protocol擴(kuò)展的加載類,第一次加載的時候Protocol還沒有自己的接口加載類,需要實(shí)例化一個。

再看new ExtensionLoader(type) 這個操作,下面為ExtensionLoader的構(gòu)造方法:

rivate ExtensionLoader(Class type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

每一個ExtensionLoader都包含2個值:type和objectFactory,此例中type就是Protocol,objectFactory就是ExtensionFactory。

對于ExtensionFactory接口來說,它的加載類中objectFactory值為null。

對于其他的接口來說,objectFactory都是通過ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()來獲?。籵bjectFactory的作用就是為dubbo的IOC提供依賴注入的對象,可以認(rèn)為是進(jìn)程內(nèi)多個組件容器的一個上層引用,

隨著這個方法的調(diào)用次數(shù)越來越多,EXTENSION_LOADERS 中存儲的 loader 也會越來越多。

自適應(yīng)擴(kuò)展類與IOC

得到ExtensionLoader組件之后,再看如何獲得自適應(yīng)擴(kuò)展實(shí)例。

public T getAdaptiveExtension() {
    //cachedAdaptiveInstance為緩存的自適應(yīng)對象,第一次調(diào)用時還沒有創(chuàng)建自適應(yīng)類,所以instance為null
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if(createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        //創(chuàng)建自適應(yīng)對象實(shí)例
                        instance = createAdaptiveExtension();
                        //將自適應(yīng)對象放到緩存中
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        }
        else {
            throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }
 
    return (T) instance;
}

首先從cachedAdaptiveInstance緩存中獲取,第一次調(diào)用時還沒有相應(yīng)的自適應(yīng)擴(kuò)展,需要創(chuàng)建自適應(yīng)實(shí)例,創(chuàng)建后再將該實(shí)例放到cachedAdaptiveInstance緩存中。

創(chuàng)建自適應(yīng)實(shí)例參考createAdaptiveExtension方法,該方法包含兩部分內(nèi)容:創(chuàng)建自適應(yīng)擴(kuò)展類并利用反射實(shí)例化、利用IOC機(jī)制為該實(shí)例注入屬性。

private T createAdaptiveExtension() {
    try {
        //得到自適應(yīng)擴(kuò)展類并利用反射實(shí)例化,然后注入屬性值
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
    }
}

再來分析getAdaptiveExtensionClass方法,以Protocol接口為例,該方法會做以下事情:獲取所有實(shí)現(xiàn)Protocol接口的擴(kuò)展類、如果有自適應(yīng)擴(kuò)展類直接返回、如果沒有則創(chuàng)建自適應(yīng)擴(kuò)展類。

//該動態(tài)代理生成的入口
private Class getAdaptiveExtensionClass() {
    //1.獲取所有實(shí)現(xiàn)Protocol接口的擴(kuò)展類
    getExtensionClasses();
    //2.如果有自適應(yīng)擴(kuò)展類,則返回
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    //3.如果沒有,則創(chuàng)建自適應(yīng)擴(kuò)展類
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

getExtensionClasses方法會加載所有實(shí)現(xiàn)Protocol接口的擴(kuò)展類,首先從緩存中獲取,緩存中沒有則調(diào)用loadExtensionClasses方法進(jìn)行加載并設(shè)置到緩存中,如下圖所示:

private Map> getExtensionClasses() {
    //從緩存中獲取
    Map> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                //從SPI配置文件中解析
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

loadExtensionClasses方法如下:首先獲取SPI注解中的value值,作為默認(rèn)擴(kuò)展名稱,在Protocol接口中SPI注解的value為dubbo,因此DubboProtocol就是Protocol的默認(rèn)實(shí)現(xiàn)擴(kuò)展。其次加載三個配置路徑下的所有的Protocol接口的擴(kuò)展實(shí)現(xiàn)。

// 此方法已經(jīng)getExtensionClasses方法同步過。
private Map> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if(value != null && (value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if(names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            if(names.length == 1) cachedDefaultName = names[0];
        }
    }
     
    Map> extensionClasses = new HashMap>();
    //分別從三個路徑加載
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}
 
 
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

在加載配置路徑下的實(shí)現(xiàn)中,其中有一個需要關(guān)注的點(diǎn),如果其中某個實(shí)現(xiàn)類上有Adaptive注解,說明用戶指定了自適應(yīng)擴(kuò)展類,那么該實(shí)現(xiàn)類就會被賦給cachedAdaptiveClass,在getAdaptiveExtensionClass方法中會被直接返回。

如果該變量為空,則需要通過字節(jié)碼工具來創(chuàng)建自適應(yīng)擴(kuò)展類。

private Class createAdaptiveExtensionClass() {
    //生成類代碼
    String code = createAdaptiveExtensionClassCode();
    //找到類加載器
    ClassLoader classLoader = findClassLoader();
    //獲取編譯器實(shí)現(xiàn)類,此處為AdaptiveCompiler,此類上有Adaptive注解
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    //將類代碼編譯為Class
    return compiler.compile(code, classLoader);
}

createAdaptiveExtensionClass方法生成的類代碼如下:

package com.alibaba.dubbo.rpc;
 
import com.alibaba.dubbo.common.extension.ExtensionLoader;
 
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
 
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
 
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
 
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

由字節(jié)碼工具生成的類Protocol$Adpative在方法末尾調(diào)用了ExtensionLoader.getExtensionLoader(xxx).getExtension(extName)來滿足adaptive的自適應(yīng)動態(tài)特性。

傳入的extName就是從url中獲取的動態(tài)參數(shù),用戶只需要在代表DUBBO全局上下文信息的URL中指定protocol參數(shù)的取值,adaptiveExtentionClass就可以去動態(tài)適配不同的擴(kuò)展實(shí)例。

再看屬性注入方法injectExtension,針對public的只有一個參數(shù)的set方法進(jìn)行處理,利用反射進(jìn)行方法調(diào)用來實(shí)現(xiàn)屬性注入,此方法是Dubbo SPI實(shí)現(xiàn)IOC功能的關(guān)鍵。

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    Class pt = method.getParameterTypes()[0];
                    try {
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;

Dubbo IOC 是通過set方法注入依賴,Dubbo首先會通過反射獲取到實(shí)例的所有方法,然后再遍歷方法列表,檢測方法名是否具有set方法特征。若有則通過ObjectFactory獲取依賴對象。

最后通過反射調(diào)用set方法將依賴設(shè)置到目標(biāo)對象中。objectFactory在創(chuàng)建加載類ExtensionLoader的時候已經(jīng)創(chuàng)建了,因?yàn)锧Adaptive是打在類AdaptiveExtensionFactory上,所以此處就是AdaptiveExtensionFactory。

AdaptiveExtensionFactory持有所有ExtensionFactory對象的集合,dubbo內(nèi)部默認(rèn)實(shí)現(xiàn)的對象工廠是SpiExtensionFactory和SpringExtensionFactory,他們經(jīng)過TreeSet排好序,查找順序是優(yōu)先先從SpiExtensionFactory獲取,如果返回空在從SpringExtensionFactory獲取。

//有Adaptive注解說明該類是自適應(yīng)類,不需要程序自己創(chuàng)建代理類
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    //factories擁有所有ExtensionFactory接口的實(shí)現(xiàn)對象
    private final List factories;
     
    public AdaptiveExtensionFactory() {
        ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List list = new ArrayList();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }
    //查找時會遍歷factories,順序優(yōu)先從SpiExtensionFactory中獲取,再從SpringExtensionFactory中獲取,原因?yàn)槌跏蓟瘯rgetSupportedExtensions方法中使用TreeSet已經(jīng)排序,見下圖
    public  T getExtension(Class type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}
public Set getSupportedExtensions() {
    Map> clazzes = getExtensionClasses();
    return Collections.unmodifiableSet(new TreeSet(clazzes.keySet()));
}

雖然有過度設(shè)計的嫌疑,但我們不得不佩服dubbo SPI設(shè)計的精巧。

  • 提供@Adaptive注解,既可以加在方法上通過參數(shù)動態(tài)適配到不同的擴(kuò)展實(shí)例;又可以加在類上直接指定自適應(yīng)擴(kuò)展類。

  • 利用AdaptiveExtensionFactory統(tǒng)一了進(jìn)程中的不同容器,將ExtensionLoader本身視為一個獨(dú)立的容器,依賴注入時將會分別從Spring容器和ExtensionLoader容器中查找。

擴(kuò)展實(shí)例和AOP

getExtension方法比較簡單,重點(diǎn)在于createExtension方法,根據(jù)擴(kuò)展名創(chuàng)建擴(kuò)展實(shí)例。

public T getExtension(String name) {
   if (name == null || name.length() == 0)
       throw new IllegalArgumentException("Extension name == null");
   if ("true".equals(name)) {
       return getDefaultExtension();
   }
   Holder holder = cachedInstances.get(name);
   if (holder == null) {
       cachedInstances.putIfAbsent(name, new Holder());
       holder = cachedInstances.get(name);
   }
   Object instance = holder.get();
   if (instance == null) {
       synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //根據(jù)擴(kuò)展名創(chuàng)建擴(kuò)展實(shí)例
                instance = createExtension(name);
                holder.set(instance);
            }
        }
   }
   return (T) instance;
}

createExtension方法中的部分內(nèi)容上文已經(jīng)分析過了,getExtensionClasses方法獲取接口的所有實(shí)現(xiàn)類,然后通過name獲取對應(yīng)的Class。緊接著通過clazz.newInstance()來實(shí)例化該實(shí)現(xiàn)類,調(diào)用injectExtension為實(shí)例注入屬性。

private T createExtension(String name) {
    //getExtensionClasses方法之前已經(jīng)分析過,獲取所有的擴(kuò)展類,然后根據(jù)擴(kuò)展名獲取對應(yīng)的擴(kuò)展類
    Class clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //屬性注入
        injectExtension(instance);
        Set> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            for (Class wrapperClass : wrapperClasses) {
                //包裝類的創(chuàng)建及屬性注入
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                type + ")  could not be instantiated: " + t.getMessage(), t);
    }
}

在方法的最后有一段對于WrapperClass包裝類的處理邏輯,如果接口存在包裝類實(shí)現(xiàn),那么就會返回包裝類實(shí)例。實(shí)現(xiàn)AOP的關(guān)鍵就是WrapperClass機(jī)制,判斷一個擴(kuò)展類是否是WrapperClass的依據(jù),是看其constructor函數(shù)中是否包含當(dāng)前接口參數(shù)。

如果有就認(rèn)為是一個wrapperClass,最終創(chuàng)建的實(shí)例是一個經(jīng)過多個wrapperClass層層包裝的結(jié)果;在每個wrapperClass中都可以編入面向切面的代碼,從而就簡單實(shí)現(xiàn)了AOP功能。

Activate活性擴(kuò)展

對應(yīng)ExtensionLoader的getActivateExtension方法,根據(jù)多個過濾條件從extension集合中智能篩選出您所需的那一部分。

getActivateExtension方法

public List getActivateExtension(URL url, String[] names, String group);

首先這個方法只會返回帶有Activate注解的擴(kuò)展類,但并非帶有注解的擴(kuò)展類都會被返回。

names是明確指定所需要的那部分?jǐn)U展類,非明確指定的擴(kuò)展類需要滿足group過濾條件和Activate注解本身指定的key過濾條件,非明確指定的會按照Activate注解中指定的排序規(guī)則進(jìn)行排序;

getActivateExtension的返回結(jié)果是上述兩種擴(kuò)展類的總和。

Activate注解類

*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    /**
     * Group過濾條件。
     */
    String[] group() default {};
 
    /**
     * Key過濾條件。包含{@link ExtensionLoader#getActivateExtension}的URL的參數(shù)Key中有,則返回擴(kuò)展。
     */
    String[] value() default {};
 
    /**
     * 排序信息,可以不提供。
     */
    String[] before() default {};
 
    /**
     * 排序信息,可以不提供。
     */
    String[] after() default {};
 
    /**
     * 排序信息,可以不提供。
     */
    int order() default 0;
}

活性Extension最典型的應(yīng)用是rpc invoke時獲取filter鏈條,各種filter有明確的執(zhí)行優(yōu)先級,同時也可以人為增添某些filter,filter還可以根據(jù)服務(wù)提供者和消費(fèi)者進(jìn)行分組過濾。

Dubbo invoke獲取filter鏈條

List filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), Constants.SERVICE_FILTER_KEY, Constants.PROVIDER);

以TokenFilter為例,其注解為@Activate(group = Constants.PROVIDER, value = Constants.TOKEN_KEY),表示該過濾器只在服務(wù)提供方才會被加載,同時會驗(yàn)證注冊地址url中是否帶了token參數(shù),如果有token表示服務(wù)端注冊時指明了要做token驗(yàn)證,自然就需要加載該filter。

反之則不用加載;此filter加載后的執(zhí)行邏輯則是從url中獲取服務(wù)端注冊時預(yù)設(shè)的token,再從rpc請求的attachments中獲取消費(fèi)方設(shè)置的remote token,比較兩者是否一致,若不一致拋出RPCExeption異常阻止消費(fèi)方的正常調(diào)用。

到此,關(guān)于“Dubbo SPI的特性及實(shí)現(xiàn)原理是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!


分享名稱:DubboSPI的特性及實(shí)現(xiàn)原理是什么
文章鏈接:http://weahome.cn/article/jpdjih.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部