dubbo之SPI(補(bǔ)充篇)

上一篇關(guān)于dubbo的SPI基本介紹完畢了,針對SPI中一些細(xì)節(jié)點(diǎn),專門開辟本文來做補(bǔ)充(補(bǔ)充點(diǎn)包括但不限于@Adaptive、@Activate、AdaptiveClassCodeGenerator的具體邏輯),后續(xù)發(fā)現(xiàn)有遺漏同樣也會加入本篇。

一、@Adaptive的處理

@Adaptive通常與ExtensionLoader.getAdaptiveExtension方法配合使用,可用于SPI擴(kuò)展類或者SPI接口方法,首先來看用于SPI擴(kuò)展類。

用于SPI擴(kuò)展類

加載SPI擴(kuò)展類過程中,會掃描擴(kuò)展類是否使用@Adaptive,若有,則初始化cachedAdaptiveClass(在此之前會完成cachedDefaultName的初始化),用于后面實(shí)例化SPI擴(kuò)展類。除此之外,同一個接口的SPI擴(kuò)展類中,有且只能有一個SPI擴(kuò)展類使用@Adaptive,否則會直接拋異常。以ExtensionFactory為例,dubbo內(nèi)置三種實(shí)現(xiàn),AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory,其中只有AdaptiveExtensionFactory使用了Adaptive注解,所以ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()返回的是AdaptiveExtensionFactory的實(shí)例

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory 
用于SPI接口方法

若SPI接口的所有實(shí)現(xiàn)類都沒有使用@Adaptive注解,那么,會在SPI接口方法上尋找@Adaptive注解。若該SPI接口的所有方法上都沒有注解(接口的所有SPI擴(kuò)展類上也沒有使用@Adaptive),則認(rèn)為該SPI接口不支持Adaptive擴(kuò)展;否則,會根據(jù)注解生成代理類源碼并編譯成class文件。以SimpleExt為例,

@SPI("impl1")
public interface SimpleExt {
    // 不指定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);
}

對應(yīng)生成的Adaptive代理類代碼如下:

package org.apache.dubbo.common.extension.ext1;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class SimpleExt$Adaptive implements  SimpleExt {

public   String yell(org.apache.dubbo.common.URL arg0,     String arg1)  {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
             org.apache.dubbo.common.URL url = arg0;
    // 獲取擴(kuò)展類實(shí)例名,優(yōu)先從url參數(shù)中取,沒有則去默認(rèn)實(shí)現(xiàn)
        String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
        if(extName == null) 
        throw new IllegalStateException("Failed to get extension        ( SimpleExt) name from url (" +      url.toString() + ") use keys([key1, key2])");
     //最終調(diào)用的還是getExtension方法
         SimpleExt extension = (SimpleExt)ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
         return extension.yell(arg0, arg1);
    }
    public   String echo(org.apache.dubbo.common.URL arg0,     String arg1)  {
            if (arg0 == null)
            throw new IllegalArgumentException("url == null");
            org.apache.dubbo.common.URL url = arg0;
        // @Adaptive注解沒有參數(shù),所以這里采用的規(guī)則是 優(yōu)先取URL中名為simple.ext(根據(jù)SimpleExt接口名轉(zhuǎn)換而來)的參數(shù)
            String extName = url.getParameter("simple.ext", "impl1");
            if(extName == null) 
        throw new IllegalStateException("Failed to get extension ( SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])");
            SimpleExt extension =   (SimpleExt)ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
            return extension.echo(arg0, arg1);
    }
  
  // 非Adaptive方法,直接拋異常
    public   String bang(org.apache.dubbo.common.URL arg0, int arg1)  {
        throw new UnsupportedOperationException("The method public abstract     String  SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface  SimpleExt is not adaptive method!");
    }
}

可以看到,接口中的方法可以分為三種,下面依次來看

1、使用Adaptive注解,且value值為空

Adaptive代理類內(nèi)部方法如下:先根據(jù)SPI接口生成參數(shù)名(舉個例子,比如這里的SimpleExt.echo方法,生成的參數(shù)名為simple.ext),然后從URL中獲取該參數(shù)值,作為方法內(nèi)當(dāng)前代理類的擴(kuò)展實(shí)現(xiàn)名(假設(shè)這里url中參數(shù)simple.ext的值為"impl1"),為空則直接拋異常;接著,通過ExtensionLoader.getExtensionLoader().getExtension()方法,獲取真正被代理的SPI實(shí)現(xiàn),并最終調(diào)用該實(shí)現(xiàn)的方法。以SimpleExt接口的echo方法為例,整個過程用代碼表示:

// 生成URL參數(shù)名
String param = "simple.ext";
// 獲取當(dāng)前SPI擴(kuò)展實(shí)現(xiàn)名
String extName = url.getParameter(param,"impl1");
// 獲取真正被代理的SPI實(shí)現(xiàn)
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
// 調(diào)用真實(shí)SPI實(shí)現(xiàn)類的方法
ext.echo();

2、使用Adaptive注解,且value值非空

注解value值非空的情況比較簡單,與第一種情況相比,少了生成URL參數(shù)名這一步。直接將注解的value值作為key,從URL中獲取當(dāng)前SPI擴(kuò)展實(shí)現(xiàn)名;這里比較有意思,若value只有一個,則要么是URL中參數(shù)名為value對應(yīng)的SPI擴(kuò)展實(shí)現(xiàn)或者默認(rèn)的SPI擴(kuò)展實(shí)現(xiàn);若value值有多個,那么dubbo獲取擴(kuò)展實(shí)現(xiàn)類的優(yōu)先級順序與value值的順序一致,比如@Adaptive("key1", "key2"),假設(shè)key1,key2對應(yīng)的SPI擴(kuò)展實(shí)現(xiàn)名分別為 "impl"和"impl2",dubbo使用真實(shí)擴(kuò)展類的順序(URL參數(shù)中key1,key2決定)會是"impl" -> "impl2" -> "默認(rèn)SPI實(shí)現(xiàn)"。這里以SimpleExt.yell方法為例,整個過程用代碼表示:

// @Adaptive("key1", "key2"),多值時支持嵌套,優(yōu)先級與值順序保持一致
// 獲取當(dāng)前SPI擴(kuò)展實(shí)現(xiàn)名
String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
// 獲取真正被代理的SPI實(shí)現(xiàn)
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
// 調(diào)用真實(shí)SPI實(shí)現(xiàn)類的方法
ext.yell();

再來看Dispatcher接口的代理類代碼,多值情況的處理更加清晰

package org.apache.dubbo.remoting;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Dispatcher$Adaptive implements org.apache.dubbo.remoting.Dispatcher {
        public org.apache.dubbo.remoting.ChannelHandler dispatch(org.apache.dubbo.remoting.ChannelHandler arg0,             org.apache.dubbo.common.URL arg1)  {
            if (arg1 == null) 
            throw new IllegalArgumentException("url == null");
            org.apache.dubbo.common.URL url = arg1;
            String extName = url.getParameter("dispatcher",url.getParameter("dispather",url.getParameter("channel.handler", "all")));
            if(extName == null) 
        throw new IllegalStateException("Failed to get extension (org.apache.dubbo.remoting.Dispatcher) name from url (" + url.toString() + ") use keys([dispatcher, dispather, channel.handler])");
            org.apache.dubbo.remoting.Dispatcher extension = (org.apache.dubbo.remoting.Dispatcher)ExtensionLoader.getExtensionLoader(org.apache.dubbo.remoting.Dispatcher.class).getExtension(extName);
            return extension.dispatch(arg0, arg1);
    }
}

可以看到這里Dispatcher的優(yōu)先級順序?yàn)椋?strong>dispatcher > dispather > channel.handler > all,其中all是Dispatcher的默認(rèn)SPI實(shí)現(xiàn)。

3、不使用Adaptive注解。

不使用@Adaptive注解的方法,則直接拋異常,比較簡單。

最后對Adaptive注解做一個總結(jié):Adaptive注解在dubbo SPI中扮演著非常重要的角色,是dubbo的SPI靈活實(shí)現(xiàn)的核心。這一特點(diǎn)在ExtensionLoader.getAdaptiveExtension方法中體現(xiàn)的淋漓盡致,尤其在@Adaptive注解用于方法時,最終生成的代理類完全支持方法級擴(kuò)展,非常靈活,這是JDK的SPI所不能比的。

二、@Activate的處理

@Activate同樣可用于類和方法(實(shí)際上并沒看到用于方法,相關(guān)處理邏輯也沒有),與@Adaptive不同,提供了基于group和value兩個維度的SPI擴(kuò)展實(shí)現(xiàn)。對Activate的處理邏輯全部在getActivateExtension方法(這里就不貼代碼了),整體上根據(jù)value中是否包含"-default"分為兩部分。

value中無"-default"

先來看value中不包含"-default"的情況,這種情況下,會直接加載SPI接口的所有擴(kuò)展類,加載過程中會判斷緩存SPI擴(kuò)展類上是否使用了注解,若有則直接放入cachedActivates緩存(注意,只有在注解的value中沒有"-default"時才會用到cachedActivates),緩存格式如下:

// 其中key是SPI擴(kuò)展類對應(yīng)的擴(kuò)展名,value則是該SPI擴(kuò)展類上的注解實(shí)例,比如<"spi",Activate@hashCode>
Map<String,Activate> cachedActivates = new HashMap();

然后通過cachedActivates跟方法參數(shù)中的group進(jìn)行匹配,匹配過程如下:遍歷cachedActivates緩存,匹配參數(shù)group與緩存實(shí)例的group值;再根據(jù)方法參數(shù)中的value值與cachedActivates緩存的SPI擴(kuò)展名匹配,加上url中的參數(shù)與cachedActivates緩存中注解的value匹配,group與value都匹配的情況下,才會放入SPI擴(kuò)展類實(shí)例結(jié)果集,并排序。聽起來可能比較復(fù)雜,這里同樣以dubbo中提供的demo ActivateExt1接口為例來分析,接口對應(yīng)有多個SPI擴(kuò)展類,對應(yīng)的SPI配置如下:

group=org.apache.dubbo.common.extension.activate.impl.GroupActivateExtImpl
value=org.apache.dubbo.common.extension.activate.impl.ValueActivateExtImpl
order1=org.apache.dubbo.common.extension.activate.impl.OrderActivateExtImpl1
order2=org.apache.dubbo.common.extension.activate.impl.OrderActivateExtImpl2
old1=org.apache.dubbo.common.extension.activate.impl.OldActivateExt1Impl2
old2=org.apache.dubbo.common.extension.activate.impl.OldActivateExt1Impl3

各擴(kuò)展類的定義如下:

// 默認(rèn)order值為0,按照order值倒序排序
@Activate(group = {"group1", "group2"})
public class GroupActivateExtImpl implements ActivateExt1 

@Activate(value = {"value"}, group = {"value"})
public class ValueActivateExtImpl implements ActivateExt1 
  
@Activate(order = 1, group = {"order"})
public class OrderActivateExtImpl1 implements ActivateExt1 
  
@Activate(order = 2, group = {"order"})
public class OrderActivateExtImpl2 implements ActivateExt1 
  
@Activate(group = "old_group")
public class OldActivateExt1Impl2 implements ActivateExt1 
  
@Activate(group = "old_group")
public class OldActivateExt1Impl3 implements ActivateExt1     

SPI擴(kuò)展類加載完畢后,cachedActivates中內(nèi)容如下(方便起見,用偽碼表示)

("group",@Activate(group = {"group1", "group2"}));
("value",@Activate(value = {"value"}, group = {"value"}));
("order1",@Activate(order = 1, group = {"order"}));
("order2",@Activate(order = 2, group = {"order"}));
("old1",@Activate(group = "old_group"));
("old2",@Activate(group = "old_group"))

此時,調(diào)用getActivateExtension(URL,"","value"),方法,假設(shè)url中參數(shù)"value"或者"*.value"值為"value",那么結(jié)果將會是value對應(yīng)的SPI擴(kuò)展類即ValueActivateExtImpl。

value中有"-default"

當(dāng)value中有"-default"時,注解中g(shù)roup值會完全被忽略,僅會根據(jù)value中的值,是否帶有remove標(biāo)志(即 "-")將SPI擴(kuò)展類實(shí)例加入結(jié)果集;注意,這里不會進(jìn)行排序,同樣以ActivateExt接口為例,此時調(diào)用getExtension(url,"ext","value"),假設(shè)url中參數(shù)"ext"值為"-default,value",那么結(jié)果同樣是value對應(yīng)的SPI擴(kuò)展類實(shí)現(xiàn)即ValueActiveExtImpl。

可以看到@Actiavte注解的處理邏輯與Adaptive完全不同,主要區(qū)別在于Adaptive提供SPI擴(kuò)展類實(shí)現(xiàn)的代理,而Activate則通過注解參數(shù)以及URL參數(shù)過濾符合要求的SPI擴(kuò)展類實(shí)現(xiàn)。二者之間同樣存在聯(lián)系,@Adaptive優(yōu)先級要高于@Activate,也就是說,如果一個SPI擴(kuò)展類實(shí)現(xiàn)上同時使用這兩個注解,那么,會優(yōu)先處理@Adaptive,而忽略對@Activate的處理(此時getActivateExtension()基本上已經(jīng)退化為getExtension()),而且,此時getAdaptiveExtension()的邏輯不受影響。

三、Wrapper代理

前面介紹過ExtensionLoader的幾個核心方法:getExtension、getDefaultExtension、getAdaptiveExtension、getActivateExtension。其中后面三個方法最終都是調(diào)getExtension實(shí)現(xiàn),前面介紹時,漏掉了對Wrapper代理類的處理,特做此補(bǔ)充。加載SPI擴(kuò)展類過程(loadClasses方法)中,會緩存代理類到cachedWrapperClasses,而代理類真正使用是在getExtension方法內(nèi)部。我們知道,初次獲取SPI實(shí)例時,會通過反射創(chuàng)建SPI實(shí)例并放入holder進(jìn)行緩存,SPI實(shí)例創(chuàng)建過程中,會判斷當(dāng)前SPI接口是否有代理類(實(shí)現(xiàn)SPI接口,構(gòu)造方法有且僅有一個參數(shù),且參數(shù)是SPI接口),若有則會對當(dāng)前SPI實(shí)例進(jìn)行代理,并最中返回代理對象。也就是說,當(dāng)前SPI接口有代理類的情況下getExtension最終返回的是一個代理對象(經(jīng)過n層代理,其中n表示代理類個數(shù))。

以Protocol為例,Protocol的SPI配置文件(僅展示代理類)內(nèi)容如下:

filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper

那么,在對應(yīng)的ExtensionLoader中,cachedWrapperClasses就會緩存ProtocolFilterWrapper、ProtocolListenerWrapper,最終getExtension返回的SPI實(shí)例就是經(jīng)過ProtocolFilterWrapper、ProtocolListenerWrapper代理的代理對象,舉個例子,執(zhí)行Protocol.export()方法時,真正執(zhí)行順序如下:ProtocolListenerWrapper.export -> ProtocolFilterWrapper.export -> DubboProtocol.export(假設(shè)這里使用默認(rèn)SPI擴(kuò)展)。最后來看核心代碼:

private T createExtension(String name) {
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        // 先從緩存取,取不到則直接通過反射創(chuàng)建
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 注入依賴的SPI實(shí)例
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        // 代理類核心邏輯,若當(dāng)前SPI接口有代理類,則創(chuàng)建代理類,注意了,這里是n層代理(取決于代理類個數(shù)),最后調(diào)用的時候會層層調(diào)用代理方法;創(chuàng)建完代理類,同樣注入代理類依賴的SPI實(shí)例,并最終返回代理類。
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        // 若有代理類,最終則會返回代理對象。
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

注:dubbo版本2.7.1,歡迎指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容