亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

解析Apache Dubbo的SPI實(shí)現(xiàn)機(jī)制

 更新時(shí)間:2021年06月22日 10:33:37   作者:vivo互聯(lián)網(wǎng)技術(shù)  
SPI全稱(chēng)為Service Provider Interface,對(duì)應(yīng)中文為服務(wù)發(fā)現(xiàn)機(jī)制。SPI類(lèi)似一種可插拔機(jī)制,首先需要定義一個(gè)接口或一個(gè)約定,然后不同的場(chǎng)景可以對(duì)其進(jìn)行實(shí)現(xiàn),調(diào)用方在使用的時(shí)候無(wú)需過(guò)多關(guān)注具體的實(shí)現(xiàn)細(xì)節(jié)

一、SPI

在Java中,SPI體現(xiàn)了面向接口編程的思想,滿(mǎn)足開(kāi)閉設(shè)計(jì)原則。

1.1、JDK自帶SPI實(shí)現(xiàn)

從JDK1.6開(kāi)始引入SPI機(jī)制后,可以看到很多使用SPI的案例,比如最常見(jiàn)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)實(shí)現(xiàn),在JDK中只定義了java.sql.Driver的接口,具體實(shí)現(xiàn)由各數(shù)據(jù)庫(kù)廠(chǎng)商來(lái)提供。下面一個(gè)簡(jiǎn)單的例子來(lái)快速了解下Java SPI的使用方式:

1)定義一個(gè)接口

package com.vivo.study
public interface Car {
void getPrice();
}

2)接口實(shí)現(xiàn)

package com.vivo.study.impl
 
/**
 * 實(shí)現(xiàn)一
 *
 */
public class AudiCar implements Car {
    @Override
    public void getPrice() {
        System.out.println("Audi A6L's price is  500000 RMB.");
    }
}
 
package com.vivo.study.impl
/**
 * 實(shí)現(xiàn)二
 *
 */
public class BydCar implements Car {
    @Override
    public void getPrice() {
        System.out.println("BYD han's price is 220000 RMB.");
    }
}

3)掛載擴(kuò)展類(lèi)信息

在META-INF/services目錄下以接口全名為文件名的文本文件,對(duì)應(yīng)此處即在META-INF/services目錄下創(chuàng)建一個(gè)文件名為com.vivo.study.Car的文件,文件內(nèi)容如下:

com.vivo.study.impl.AudiCar
com.vivo.study.impl.BydCar

4)使用

public class SpiDemo {
public static void main(String[] args) {
        ServiceLoader<Car> load = ServiceLoader.load(Car.class);
        Iterator<Car> iterator = load.iterator();
while (iterator.hasNext()) {
            Car car = iterator.next();
            car.getPrice();
        }
    }
}

上面的例子簡(jiǎn)單的介紹了JDK SPI機(jī)制的使用方式,其中最關(guān)鍵的類(lèi)為ServiceLoader,通過(guò)ServiceLoader類(lèi)來(lái)加載接口的實(shí)現(xiàn)類(lèi),ServiceLoader是Iterable接口的實(shí)現(xiàn)類(lèi),對(duì)于ServiceLoader加載的詳細(xì)過(guò)程此處不展開(kāi)。

JDK對(duì)SPI的加載實(shí)現(xiàn)存在一個(gè)較為突出的小缺點(diǎn),無(wú)法按需加載實(shí)現(xiàn)類(lèi),通過(guò)ServiceLoader.load加載時(shí)會(huì)將文件中的所有實(shí)現(xiàn)都進(jìn)行實(shí)例化,如果想要獲取具體某個(gè)具體的實(shí)現(xiàn)類(lèi)需要進(jìn)行遍歷判斷。

1.2、Dubbo SPI

SPI擴(kuò)展是Dubbo的最大的優(yōu)點(diǎn)之一,支持協(xié)議擴(kuò)展、調(diào)用攔截?cái)U(kuò)展、引用監(jiān)聽(tīng)擴(kuò)展等等。在Dubbo中,根據(jù)不同的擴(kuò)展定位,擴(kuò)展文件分別被放置在META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/這三個(gè)路徑下。

Dubbo中有直接使用JDK SPI實(shí)現(xiàn)的方式,比如org.apache.dubbo.common.extension.LoadingStrategy放在META-INF/services/路徑下,但大多情況下都是使用其自身對(duì)JDK SPI的實(shí)現(xiàn)的一種優(yōu)化方式,可稱(chēng)為Dubbo SPI,也就是本文要講解的點(diǎn)。

相比于JDK的SPI的實(shí)現(xiàn),Dubbo SPI具有以下特點(diǎn):

  • 配置形式更靈活:支持以key:value的形式在文件里配置類(lèi)似name:xxx.xxx.xxx.xx,后續(xù)可以通過(guò)name來(lái)進(jìn)行擴(kuò)展類(lèi)按需精準(zhǔn)獲取。
  • 緩存的使用:使用緩存提升性能,保證一個(gè)擴(kuò)展實(shí)現(xiàn)類(lèi)至多會(huì)加載一次。
  • 對(duì)擴(kuò)展類(lèi)細(xì)分?jǐn)U展:支持?jǐn)U展點(diǎn)自動(dòng)包裝(Wrapper)、擴(kuò)展點(diǎn)自動(dòng)裝配、擴(kuò)展點(diǎn)自適應(yīng)(@Adaptive)、擴(kuò)展點(diǎn)自動(dòng)激活(@Activate)。

Dubbo對(duì)擴(kuò)展點(diǎn)的加載主要由ExtensionLoader這個(gè)類(lèi)展開(kāi)。

二、加載-ExtensionLoader

ExtensionLoader在Dubbo里的角色類(lèi)似ServiceLoader在JDK中的存在,用于加載擴(kuò)展類(lèi)。在Dubbo源碼里,隨處都可以見(jiàn)到ExtensionLoader的身影,比如在服務(wù)暴露里的關(guān)鍵類(lèi)ServiceConfig中等,弄清楚ExtensionLoader的實(shí)現(xiàn)細(xì)節(jié)對(duì)閱讀Dubbo源碼有很大的幫助。

2.1、獲取ExtensionLoader的實(shí)例

ExtensionLoader沒(méi)有提供共有的構(gòu)造函數(shù),

只能通過(guò)ExtensionLoader.getExtensionLoader(Class type)來(lái)獲取ExtensionLoader實(shí)例。

public
 
    // ConcurrentHashMap緩存,key -> Class value -> ExtensionLoader實(shí)例
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
 
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
 
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        // 檢查是否是接口,如果不是則拋出異常
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
 
        // 檢查接口是否是被@SPI注解修飾,如果沒(méi)有則拋出異常
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }
 
        // 從緩存里取,沒(méi)有則初始化一個(gè)并放入緩存EXTENSION_LOADERS中
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
}

上面的代碼展示了獲取ExtensionLoader實(shí)例的過(guò)程,可以看出,每一個(gè)被@SPI修飾的接口都會(huì)對(duì)應(yīng)同一個(gè)ExtensionLoader實(shí)例,且對(duì)應(yīng)ExtensionLoader只會(huì)被初始化一次,并緩存在ConcurresntHashMap中。

2.2、加載擴(kuò)展類(lèi)

加載擴(kuò)展類(lèi)入口,當(dāng)使用ExtensionLoader時(shí),getExtensionName、getActivateExtension或是getDefaultExtension都要經(jīng)過(guò)getExtensionClasses方法來(lái)加載擴(kuò)展類(lèi),如下圖;

getExtensionClasses方法調(diào)用的路徑如下圖,getExtensionClasses是加載擴(kuò)展類(lèi)的一個(gè)起點(diǎn),會(huì)首先從緩存中獲取,如果緩存中沒(méi)有則通過(guò)loadExtensionClasses方法來(lái)加載擴(kuò)展類(lèi),所以說(shuō)實(shí)際上的加載邏輯入口在loadExtensionClasses。

getExtensionClasses

  |->loadExtensionClasses

    |->cacheDefaultExtensionName

    |->loadDirectory

      |->loadResource

        |->loadClass

2.2.1、loadExtensionClasses加載擴(kuò)展類(lèi)

由于整個(gè)加載過(guò)程設(shè)計(jì)的源碼較多,因此用一個(gè)流程圖來(lái)進(jìn)行描述,具體細(xì)節(jié)可以結(jié)合源碼進(jìn)行查看。

loadExtensionClasses主要做了以下這幾件事:

默認(rèn)擴(kuò)展名:

抽取默認(rèn)擴(kuò)展實(shí)現(xiàn)名并緩存在ExtensionLoader里的cachedDefaultName,默認(rèn)擴(kuò)展名配置通過(guò)@SPI注解在接口上配置,如配置@SPI("defaultName")則默認(rèn)擴(kuò)展名為defaultName。

加載擴(kuò)展類(lèi)信息:

從META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/這三個(gè)路徑下尋找以類(lèi)的全路徑名命名的文件,并逐行讀取文件里的內(nèi)容。

加載class并緩存:

對(duì)擴(kuò)展類(lèi)分為自適應(yīng)擴(kuò)展實(shí)現(xiàn)類(lèi)(被@Adaptive修飾的實(shí)現(xiàn)類(lèi))、包裝類(lèi)(擁有一個(gè)只有一個(gè)為這個(gè)接口類(lèi)型的參數(shù)的構(gòu)造方法)、普通擴(kuò)展類(lèi),其中普通擴(kuò)展類(lèi)中又包含自動(dòng)激活擴(kuò)展類(lèi)(被@Activate修飾的類(lèi))和真普通的類(lèi),對(duì)自適應(yīng)擴(kuò)展實(shí)現(xiàn)類(lèi)、包裝類(lèi)、自動(dòng)激活擴(kuò)展類(lèi)這三種類(lèi)型的類(lèi)分別加載并分別緩存到cachedAdaptiveClass、cachedActivates、cachedWrapperClasses。

返回結(jié)果Map<String, Class<?>>:

結(jié)果返回Map,其中key對(duì)應(yīng)擴(kuò)展文件里配置的name,value對(duì)應(yīng)擴(kuò)展的類(lèi)class,最后在getExtensionClasses方法里會(huì)將此結(jié)果放入緩存cachedClasses中。此結(jié)果Map中包含除了自適應(yīng)擴(kuò)展實(shí)現(xiàn)類(lèi)和包裝實(shí)現(xiàn)類(lèi)的其他所用的擴(kuò)展類(lèi)名與對(duì)應(yīng)類(lèi)的映射關(guān)系。

通過(guò)loadExtensionClasses方法把擴(kuò)展類(lèi)(Class對(duì)象)都加載到相應(yīng)的緩存中,是為了方便后面實(shí)例化擴(kuò)展類(lèi)對(duì)象,通過(guò)newInstance()等方法來(lái)實(shí)例化。

2.2.2、擴(kuò)展包裝類(lèi)

什么是擴(kuò)展包裝類(lèi)?是不是類(lèi)名結(jié)尾包含了Wrapper的類(lèi)就是擴(kuò)展包裝類(lèi)?

在Dubbo SPI接口的實(shí)現(xiàn)擴(kuò)展類(lèi)中,如果此類(lèi)包含一個(gè)此接口作為參數(shù)的構(gòu)造方法,則為擴(kuò)展包裝類(lèi)。擴(kuò)展包裝類(lèi)的作用是持有具體的擴(kuò)展實(shí)現(xiàn)類(lèi),可以一層一層的被包裝,作用類(lèi)似AOP。

包裝擴(kuò)展類(lèi)的作用是類(lèi)似AOP,方便擴(kuò)展增強(qiáng)。具體實(shí)現(xiàn)代碼如下:

從代碼中可以得出,可以通過(guò)boolean wrap選擇是否使用包裝類(lèi),默認(rèn)情況下為true;如果有擴(kuò)展包裝類(lèi),實(shí)際的實(shí)現(xiàn)類(lèi)會(huì)被包裝類(lèi)按一定的順序一層一層包起來(lái)。

如Protocol的實(shí)現(xiàn)類(lèi)ProtocolFilterWrapper、ProtocolListenerWrapper都是擴(kuò)展包裝類(lèi)。

2.2.3、自適應(yīng)擴(kuò)展實(shí)現(xiàn)類(lèi)

@Adaptive

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}

從源碼以及源碼注釋中可以得出以下幾點(diǎn):

  • Adaptive是一個(gè)注解,可以修飾類(lèi)(接口,枚舉)和方法。
  • 此注解的作用是為ExtensionLoader注入擴(kuò)展實(shí)例提供有用的信息。

從注釋中理解value的作用:

  • value可以決定選擇使用具體的擴(kuò)展類(lèi)。
  • 通過(guò)value配置的值key,在修飾的方法的入?yún)rg.apache.dubbo.common.URL中通過(guò)key獲取到對(duì)應(yīng)的值value,根據(jù)value作為extensionName去決定使用對(duì)應(yīng)的擴(kuò)展類(lèi)。
  • 如果通過(guò)2沒(méi)有找到對(duì)應(yīng)的擴(kuò)展,會(huì)選擇默認(rèn)的擴(kuò)展類(lèi),通過(guò)@SPI配置默認(rèn)擴(kuò)展類(lèi)。

@Adaptive簡(jiǎn)單例子

由于@Adaptive修飾類(lèi)時(shí)比較好理解,這里舉一個(gè)@Adaptive修飾方法的例子,使用@Adaptive修飾方法的這種情況在Dubbo也是隨處可見(jiàn)。

/**
* Dubbo SPI 接口
*/
@SPI("impl1")
public interface SimpleExt {
    @Adaptive({"key1", "key2"})
    String yell(URL url, String s);
}

如果調(diào)用

ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension().yell(url, s)方法,最終調(diào)用哪一個(gè)擴(kuò)展類(lèi)的實(shí)例去執(zhí)行yell方法的流程大致為:先獲取擴(kuò)展類(lèi)的名稱(chēng)extName(對(duì)應(yīng)上面說(shuō)的name:class中的name),然后通過(guò)extName來(lái)獲取對(duì)應(yīng)的類(lèi)Class,再實(shí)例化進(jìn)行調(diào)用。所以關(guān)鍵的步驟在怎么得到extName,上面的這個(gè)例子得到extName的流程為:

  • 通過(guò)url.getParameters.get("key1")獲取,
  • 沒(méi)有獲取到則用url.getParameters.get("key2"),如果還是沒(méi)有獲取到則使用impl1對(duì)應(yīng)的實(shí)現(xiàn)類(lèi),最后還是沒(méi)有獲取到則拋異常IllegalStateException。

可以看出,@Adaptive的好處就是可以通過(guò)方法入?yún)Q定具體調(diào)用哪一個(gè)實(shí)現(xiàn)類(lèi)。下面會(huì)對(duì)@Adaptive的具體實(shí)現(xiàn)進(jìn)行詳細(xì)分析。

@Adaptive加載流程

流程關(guān)鍵點(diǎn)說(shuō)明:

1)黃色標(biāo)記的,cachedAdaptiveClass是在ExtensionLoader#loadClass方法中加載Extension類(lèi)時(shí)緩存的。

2)綠色標(biāo)記的,如果Extension類(lèi)中存在被@Adaptive修飾的類(lèi)時(shí)會(huì)使用該類(lèi)來(lái)初始化實(shí)例。

3)紅色標(biāo)記的,如果Extension類(lèi)中不存在被@Adaptive修飾的類(lèi)時(shí),則需要?jiǎng)討B(tài)生成代碼,通過(guò)javassist(默認(rèn))來(lái)編譯生成Xxxx$Adaptive類(lèi)來(lái)實(shí)例化。

4)實(shí)例化后通過(guò)injectExtension來(lái)將Adaptive實(shí)例的Extension注入(屬性注入)。

后續(xù)圍繞上述的關(guān)鍵點(diǎn)3詳細(xì)展開(kāi),關(guān)鍵點(diǎn)4此處不展開(kāi)。

動(dòng)態(tài)生成Xxx$Adaptive類(lèi):下面的代碼為動(dòng)態(tài)生成Adaptive類(lèi)的相關(guān)代碼,具體生成代碼的細(xì)節(jié)在A(yíng)daptiveClassCodeGenerator#generate中

public class ExtensionLoader<T> {
// ...
private Class<?> getAdaptiveExtensionClass() {
// 根據(jù)對(duì)應(yīng)的SPI文件加載擴(kuò)展類(lèi)并緩存,細(xì)節(jié)此處不展開(kāi)
        getExtensionClasses();
// 如果存在被@Adaptive修飾的類(lèi)則直接返回此類(lèi)
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
        }
// 動(dòng)態(tài)生成Xxxx$Adaptive類(lèi)
return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
private Class<?> createAdaptiveExtensionClass() {
// 生成Xxxx$Adaptive類(lèi)代碼,可自行加日志或斷點(diǎn)查看生成的代碼
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
// 獲取動(dòng)態(tài)編譯器,默認(rèn)為javassist
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
    }
}

AdaptiveClassCodeGenerator#generate生成code的方式是通過(guò)字符串拼接,大量使用String.format,整個(gè)代碼過(guò)程比較繁瑣,可通過(guò)debug去了解細(xì)節(jié)。

最關(guān)鍵的的部分是生成被@Adaptive修飾的方法的內(nèi)容,也就是最終調(diào)用實(shí)例的@Adaptive方法時(shí),可通過(guò)參數(shù)來(lái)動(dòng)態(tài)選擇具體使用哪個(gè)擴(kuò)展實(shí)例。下面對(duì)此部分進(jìn)行分析:

public class AdaptiveClassCodeGenerator {
// ...
/**
     * generate method content
     */
private String generateMethodContent(Method method) {
// 獲取方法上的@Adaptive注解
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
// 方法時(shí)沒(méi)有@Adaptive注解,生成不支持的代碼
return generateUnsupported(method);
        } else {
// 方法參數(shù)里URL是第幾個(gè)參數(shù),不存在則為-1
int urlTypeIndex = getUrlTypeIndex(method);
// found parameter in URL type
if (urlTypeIndex != -1) {
// Null Point check
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
// did not find parameter in URL type
                code.append(generateUrlAssignmentIndirectly(method));
            }
// 獲取方法上@Adaptive配置的value
// 比如 @Adaptive({"key1","key2"}),則會(huì)返回String數(shù)組{"key1","key2"}
// 如果@Adaptive沒(méi)有配置value,則會(huì)根據(jù)簡(jiǎn)寫(xiě)接口名按駝峰用.分割,比如SimpleExt對(duì)應(yīng)simple.ext
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// 參數(shù)里是否存在org.apache.dubbo.rpc.Invocation
            boolean hasInvocation = hasInvocationArgument(method);
            code.append(generateInvocationArgumentNullCheck(method));
// 生成String extName = xxx;的代碼 ,extName用于獲取具體的Extension實(shí)例
            code.append(generateExtNameAssignment(value, hasInvocation));
// check extName == null?
            code.append(generateExtNameNullCheck(value));
            code.append(generateExtensionAssignment());
// return statement
            code.append(generateReturnAndInvocation(method));
        }
return code.toString();
    }
}

上述生成Adaptive類(lèi)的方法內(nèi)容中最關(guān)鍵的步驟在生成extName的部分,也就是generateExtNameAssignment(value,hasInvocation),此方法if太多了(有點(diǎn)眼花繚亂)。

以下列舉幾個(gè)例子對(duì)此方法的實(shí)現(xiàn)流程進(jìn)行簡(jiǎn)單展示:假設(shè)方法中的參數(shù)不包含org.apache.dubbo.rpc.Invocation,包含org.apache.dubbo.rpc.Invocation的情況會(huì)更加復(fù)雜。

1)方法被@Adaptive修飾,沒(méi)有配置value,且在接口@SPI上配置了默認(rèn)的實(shí)現(xiàn)

@SPI("impl1")
public interface SimpleExt {
@Adaptive
String echo(URL url, String s);
}

對(duì)應(yīng)生成extName的代碼為:

String extName = url.getParameter("simple.ext", "impl1")

2)方法被@Adaptive修飾,沒(méi)有配置value,且在接口@SPI上沒(méi)有配置默認(rèn)的實(shí)現(xiàn)

@SPI("impl1")
public interface SimpleExt {
@Adaptive({"key1", "key2"})
String yell(URL url, String s);
}

對(duì)應(yīng)生成extName的代碼為:

String extName = url.getParameter( "simple.ext")

3)方法被@Adaptive修飾,配置了value(假設(shè)兩個(gè),依次類(lèi)推),且在接口@SPI上配置了默認(rèn)的實(shí)現(xiàn)

@SPI
public interface SimpleExt {
@Adaptive({"key1", "key2"})
String yell(URL url, String s);
}

對(duì)應(yīng)生成extName的代碼為:

String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));

4)方法被@Adaptive修飾,配置了value(假設(shè)兩個(gè),依次類(lèi)推),且在接口@SPI沒(méi)有配置默認(rèn)的實(shí)現(xiàn)

@SPI
public interface SimpleExt {
@Adaptive({"key1", "key2"})
String yell(URL url, String s);
}

對(duì)應(yīng)生成extName的代碼為:

String extName = url.getParameter("key1", url.getParameter("key2"));

完整的生成類(lèi)可參見(jiàn)附錄。

2.2.4、自動(dòng)激活擴(kuò)展類(lèi)

如果你有擴(kuò)展實(shí)現(xiàn)過(guò)Dubbo的Filter,那么一定會(huì)對(duì)@Activate很熟悉。@Activate注解的作用是可以通過(guò)給定的條件來(lái)自動(dòng)激活擴(kuò)展實(shí)現(xiàn)類(lèi),通過(guò)ExtensionLoader#getActivateExtension(URL,String, String)方法可以找到指定條件下需要激活的擴(kuò)展類(lèi)列表。

下面以一個(gè)例子來(lái)對(duì)@Activate的作用進(jìn)行說(shuō)明,在Consumer調(diào)用Dubbo接口時(shí),會(huì)經(jīng)過(guò)消費(fèi)方的過(guò)濾器鏈以及提供方的過(guò)濾器鏈,在Provider暴露服務(wù)的過(guò)程中會(huì)拼接需要使用哪些Filter。

對(duì)應(yīng)源碼中的位置在ProtocolFilterWrapper#buildInvokerChain(invoker, key, group)方法中。

// export:key-> service.filter ; group-> provider
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    // 在Provider暴露服務(wù)服務(wù)export時(shí),會(huì)根據(jù)獲取Url中的service.filter對(duì)應(yīng)的值和group=provider來(lái)獲取激活對(duì)應(yīng)的Filter
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
}

ExtensionLoader#getActivateExtension(URL, String, String)是怎么根據(jù)條件來(lái)自動(dòng)激活對(duì)應(yīng)的擴(kuò)展類(lèi)列表的可以自行查看該方法的代碼,此處不展開(kāi)。

三、總結(jié)

本文主要對(duì)Dubbo SPI機(jī)制的擴(kuò)展類(lèi)加載過(guò)程通過(guò)ExtensionLoader類(lèi)源碼來(lái)進(jìn)行總結(jié),可以概況為以下幾點(diǎn):

1.Dubbo SPI結(jié)合了JDK SPI的實(shí)現(xiàn),并在此基礎(chǔ)上進(jìn)行優(yōu)化,如精準(zhǔn)按需加載擴(kuò)展類(lèi)、緩存提升性能。

2.分析ExtensionLoader加載擴(kuò)展類(lèi)的過(guò)程,加載META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/這三個(gè)路徑下的文件,并分類(lèi)緩存在ExtensionLoader實(shí)例。

3.介紹擴(kuò)展包裝類(lèi)及其實(shí)現(xiàn)過(guò)程,擴(kuò)展包裝類(lèi)實(shí)現(xiàn)了類(lèi)似AOP的功能。

4.自適應(yīng)擴(kuò)展類(lèi),分析@Adptive修飾方法時(shí)動(dòng)態(tài)生成Xxx$Adaptive類(lèi)的過(guò)程,以及通過(guò)參數(shù)自適應(yīng)選擇擴(kuò)展實(shí)現(xiàn)類(lèi)完成方法調(diào)用的案例介紹。

簡(jiǎn)單介紹自動(dòng)激活擴(kuò)展類(lèi)及@Activate的作用。

四、附錄

Xxx$Adaptive完整案例

@SPI接口定義

@SPI("impl1")
public interface SimpleExt {
    // @Adaptive example, do not specify a explicit key.
    @Adaptive
    String echo(URL url, String s);
 
    @Adaptive({"key1", "key2"})
    String yell(URL url, String s);
 
    // no @Adaptive
    String bang(URL url, int i);
}

生成的Adaptive類(lèi)代碼

package org.apache.dubbo.common.extension.ext1;
  
import org.apache.dubbo.common.extension.ExtensionLoader;
  
public class SimpleExt$Adaptive implements org.apache.dubbo.common.extension.ext1.SimpleExt {
  
    public java.lang.String yell(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key1, key2])");
        org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
        return extension.yell(arg0, arg1);
    }
  
    public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("simple.ext", "impl1");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])");
        org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
        return extension.echo(arg0, arg1);
    }
  
    public java.lang.String bang(org.apache.dubbo.common.URL arg0, int arg1) {
        throw new UnsupportedOperationException("The method public abstract java.lang.String org.apache.dubbo.common.extension.ext1.SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface org.apache.dubbo.common.extension.ext1.SimpleExt is not adaptive method!");
    }
  
}

以上就是解析Apache Dubbo的SPI實(shí)現(xiàn)機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于A(yíng)pache Dubbo的SPI實(shí)現(xiàn)機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • IO流概述分類(lèi)字節(jié)流寫(xiě)數(shù)據(jù)三種方式及問(wèn)題分析

    IO流概述分類(lèi)字節(jié)流寫(xiě)數(shù)據(jù)三種方式及問(wèn)題分析

    這篇文章主要為大家介紹了IO流概述分類(lèi)字節(jié)流寫(xiě)數(shù)據(jù)三種方式及寫(xiě)數(shù)據(jù)問(wèn)題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 使用IntelliJ IDEA2020.2.2 x64 新建java項(xiàng)目并且輸出Hello World

    使用IntelliJ IDEA2020.2.2 x64 新建java項(xiàng)目并且輸出Hello World

    這篇文章主要介紹了使用IntelliJ IDEA2020.2.2 x64 新建java項(xiàng)目并且輸出Hello World,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Java JSch遠(yuǎn)程執(zhí)行Shell命令的方法

    Java JSch遠(yuǎn)程執(zhí)行Shell命令的方法

    本文主要介紹了Java JSch遠(yuǎn)程執(zhí)行Shell命令,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • MybatisPlus中selectPage的使用方法

    MybatisPlus中selectPage的使用方法

    本文主要介紹了MybatisPlus中selectPage的使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • 淺析java 10中的var關(guān)鍵字用法

    淺析java 10中的var關(guān)鍵字用法

    2018年3月20日,Oracle發(fā)布java10。java10為java帶來(lái)了很多新特性。這篇文章主要介紹了Java 10 var關(guān)鍵字詳解和示例教程,需要的朋友可以參考下
    2018-10-10
  • 線(xiàn)程池中execute與submit的區(qū)別說(shuō)明

    線(xiàn)程池中execute與submit的區(qū)別說(shuō)明

    這篇文章主要介紹了線(xiàn)程池execute與submit的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java如何更改數(shù)據(jù)庫(kù)中的數(shù)據(jù)

    java如何更改數(shù)據(jù)庫(kù)中的數(shù)據(jù)

    這篇文章主要介紹了java如何更改數(shù)據(jù)庫(kù)中的數(shù)據(jù),修改數(shù)據(jù)庫(kù)是數(shù)據(jù)庫(kù)操作必不可少的一部分,使用Statement接口中的excuteUpdate()方法可以修改數(shù)據(jù)表中的數(shù)據(jù),感興趣的朋友跟隨小編一起看看吧
    2021-11-11
  • springboot2+mybatis多種方式實(shí)現(xiàn)多數(shù)據(jù)配置方法

    springboot2+mybatis多種方式實(shí)現(xiàn)多數(shù)據(jù)配置方法

    這篇文章主要介紹了springboot2+mybatis多種方式實(shí)現(xiàn)多數(shù)據(jù)配置方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Java內(nèi)省之Introspector解讀

    Java內(nèi)省之Introspector解讀

    這篇文章主要介紹了Java內(nèi)省之Introspector解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • 基于FLink實(shí)現(xiàn)實(shí)時(shí)安全檢測(cè)的示例代碼

    基于FLink實(shí)現(xiàn)實(shí)時(shí)安全檢測(cè)的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何基于FLink實(shí)現(xiàn)實(shí)時(shí)安全檢測(cè)的功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的可以了解一下
    2023-02-02

最新評(píng)論