dubbo之SPI(补充篇)

上一篇关于dubbo的SPI基本介绍完毕了,针对SPI中一些细节点,专门开辟本文来做补充(补充点包括但不限于@Adaptive、@Activate、AdaptiveClassCodeGenerator的具体逻辑),后续发现有遗漏同样也会加入本篇。

一、@Adaptive的处理

@Adaptive通常与ExtensionLoader.getAdaptiveExtension方法配合使用,可用于SPI扩展类或者SPI接口方法,首先来看用于SPI扩展类。

用于SPI扩展类

加载SPI扩展类过程中,会扫描扩展类是否使用@Adaptive,若有,则初始化cachedAdaptiveClass(在此之前会完成cachedDefaultName的初始化),用于后面实例化SPI扩展类。除此之外,同一个接口的SPI扩展类中,有且只能有一个SPI扩展类使用@Adaptive,否则会直接抛异常。以ExtensionFactory为例,dubbo内置三种实现,AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory,其中只有AdaptiveExtensionFactory使用了Adaptive注解,所以ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()返回的是AdaptiveExtensionFactory的实例

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

若SPI接口的所有实现类都没有使用@Adaptive注解,那么,会在SPI接口方法上寻找@Adaptive注解。若该SPI接口的所有方法上都没有注解(接口的所有SPI扩展类上也没有使用@Adaptive),则认为该SPI接口不支持Adaptive扩展;否则,会根据注解生成代理类源码并编译成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);
}

对应生成的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;
    // 获取扩展类实例名,优先从url参数中取,没有则去默认实现
        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])");
     //最终调用的还是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注解没有参数,所以这里采用的规则是 优先取URL中名为simple.ext(根据SimpleExt接口名转换而来)的参数
            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代理类内部方法如下:先根据SPI接口生成参数名(举个例子,比如这里的SimpleExt.echo方法,生成的参数名为simple.ext),然后从URL中获取该参数值,作为方法内当前代理类的扩展实现名(假设这里url中参数simple.ext的值为"impl1"),为空则直接抛异常;接着,通过ExtensionLoader.getExtensionLoader().getExtension()方法,获取真正被代理的SPI实现,并最终调用该实现的方法。以SimpleExt接口的echo方法为例,整个过程用代码表示:

// 生成URL参数名
String param = "simple.ext";
// 获取当前SPI扩展实现名
String extName = url.getParameter(param,"impl1");
// 获取真正被代理的SPI实现
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
// 调用真实SPI实现类的方法
ext.echo();

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

注解value值非空的情况比较简单,与第一种情况相比,少了生成URL参数名这一步。直接将注解的value值作为key,从URL中获取当前SPI扩展实现名;这里比较有意思,若value只有一个,则要么是URL中参数名为value对应的SPI扩展实现或者默认的SPI扩展实现;若value值有多个,那么dubbo获取扩展实现类的优先级顺序与value值的顺序一致,比如@Adaptive("key1", "key2"),假设key1,key2对应的SPI扩展实现名分别为 "impl"和"impl2",dubbo使用真实扩展类的顺序(URL参数中key1,key2决定)会是"impl" -> "impl2" -> "默认SPI实现"。这里以SimpleExt.yell方法为例,整个过程用代码表示:

// @Adaptive("key1", "key2"),多值时支持嵌套,优先级与值顺序保持一致
// 获取当前SPI扩展实现名
String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
// 获取真正被代理的SPI实现
SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getExtension(extName);
// 调用真实SPI实现类的方法
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的优先级顺序为:dispatcher > dispather > channel.handler > all,其中all是Dispatcher的默认SPI实现。

3、不使用Adaptive注解。

不使用@Adaptive注解的方法,则直接抛异常,比较简单。

最后对Adaptive注解做一个总结:Adaptive注解在dubbo SPI中扮演着非常重要的角色,是dubbo的SPI灵活实现的核心。这一特点在ExtensionLoader.getAdaptiveExtension方法中体现的淋漓尽致,尤其在@Adaptive注解用于方法时,最终生成的代理类完全支持方法级扩展,非常灵活,这是JDK的SPI所不能比的。

二、@Activate的处理

@Activate同样可用于类和方法(实际上并没看到用于方法,相关处理逻辑也没有),与@Adaptive不同,提供了基于group和value两个维度的SPI扩展实现。对Activate的处理逻辑全部在getActivateExtension方法(这里就不贴代码了),整体上根据value中是否包含"-default"分为两部分。

value中无"-default"

先来看value中不包含"-default"的情况,这种情况下,会直接加载SPI接口的所有扩展类,加载过程中会判断缓存SPI扩展类上是否使用了注解,若有则直接放入cachedActivates缓存(注意,只有在注解的value中没有"-default"时才会用到cachedActivates),缓存格式如下:

// 其中key是SPI扩展类对应的扩展名,value则是该SPI扩展类上的注解实例,比如<"spi",Activate@hashCode>
Map cachedActivates = new HashMap();

然后通过cachedActivates跟方法参数中的group进行匹配,匹配过程如下:遍历cachedActivates缓存,匹配参数group与缓存实例的group值;再根据方法参数中的value值与cachedActivates缓存的SPI扩展名匹配,加上url中的参数与cachedActivates缓存中注解的value匹配,group与value都匹配的情况下,才会放入SPI扩展类实例结果集,并排序。听起来可能比较复杂,这里同样以dubbo中提供的demo ActivateExt1接口为例来分析,接口对应有多个SPI扩展类,对应的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

各扩展类的定义如下:

// 默认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扩展类加载完毕后,cachedActivates中内容如下(方便起见,用伪码表示)

("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"))

此时,调用getActivateExtension(URL,"","value"),方法,假设url中参数"value"或者"*.value"值为"value",那么结果将会是value对应的SPI扩展类即ValueActivateExtImpl。

value中有"-default"

当value中有"-default"时,注解中group值会完全被忽略,仅会根据value中的值,是否带有remove标志(即 "-")将SPI扩展类实例加入结果集;注意,这里不会进行排序,同样以ActivateExt接口为例,此时调用getExtension(url,"ext","value"),假设url中参数"ext"值为"-default,value",那么结果同样是value对应的SPI扩展类实现即ValueActiveExtImpl。

可以看到@Actiavte注解的处理逻辑与Adaptive完全不同,主要区别在于Adaptive提供SPI扩展类实现的代理,而Activate则通过注解参数以及URL参数过滤符合要求的SPI扩展类实现。二者之间同样存在联系,@Adaptive优先级要高于@Activate,也就是说,如果一个SPI扩展类实现上同时使用这两个注解,那么,会优先处理@Adaptive,而忽略对@Activate的处理(此时getActivateExtension()基本上已经退化为getExtension()),而且,此时getAdaptiveExtension()的逻辑不受影响。

三、Wrapper代理

前面介绍过ExtensionLoader的几个核心方法:getExtension、getDefaultExtension、getAdaptiveExtension、getActivateExtension。其中后面三个方法最终都是调getExtension实现,前面介绍时,漏掉了对Wrapper代理类的处理,特做此补充。加载SPI扩展类过程(loadClasses方法)中,会缓存代理类到cachedWrapperClasses,而代理类真正使用是在getExtension方法内部。我们知道,初次获取SPI实例时,会通过反射创建SPI实例并放入holder进行缓存,SPI实例创建过程中,会判断当前SPI接口是否有代理类(实现SPI接口,构造方法有且仅有一个参数,且参数是SPI接口),若有则会对当前SPI实例进行代理,并最中返回代理对象。也就是说,当前SPI接口有代理类的情况下getExtension最终返回的是一个代理对象(经过n层代理,其中n表示代理类个数)。

以Protocol为例,Protocol的SPI配置文件(仅展示代理类)内容如下:

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

那么,在对应的ExtensionLoader中,cachedWrapperClasses就会缓存ProtocolFilterWrapper、ProtocolListenerWrapper,最终getExtension返回的SPI实例就是经过ProtocolFilterWrapper、ProtocolListenerWrapper代理的代理对象,举个例子,执行Protocol.export()方法时,真正执行顺序如下:ProtocolListenerWrapper.export -> ProtocolFilterWrapper.export -> DubboProtocol.export(假设这里使用默认SPI扩展)。最后来看核心代码:

private T createExtension(String name) {
    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, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 注入依赖的SPI实例
        injectExtension(instance);
        Set> wrapperClasses = cachedWrapperClasses;
        // 代理类核心逻辑,若当前SPI接口有代理类,则创建代理类,注意了,这里是n层代理(取决于代理类个数),最后调用的时候会层层调用代理方法;创建完代理类,同样注入代理类依赖的SPI实例,并最终返回代理类。
        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,欢迎指正。

你可能感兴趣的:(dubbo之SPI(补充篇))