Dubbo设计之ExtensionLoader

导读

  • 想要搞懂Dubbo底层实现,ExtensionLoader是不可绕过得门槛,不能深刻理解其扩展点设计,源码阅读部分会很懵逼!!!(当然,即使扩展点懂了,源码也不一定能看懂,哈哈。玩笑话,意思就是Dubbo得源码还是比较难读的,因为有很多概念与设计如果不弄清楚,基本上会被绕晕。)
  • 关键字Dubbo 扩展点设计(SPI)、ExtensionLoader

Dubbo 扩展点定义

  • 从 ExtensionLoader的源码中,我们可以找到 Dubbo SPI加载的目录有三个:1. META-INF/services/ (标准的SPI路径)2. META-INF/dubbo/ 3. META-INF/dubbo/internal/ (内部实现)
  • 扩展点文件定义格式:目录 / 接口全路径
  • 文件内容(常用格式)
       registry=com.apache.dubbo.registry.integration.RegistryProtocol
       dubbo=com.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
       filter=com.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
       listener=com.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
    
       # 没有等号情况
       com.apache.dubbo.registry.integration.XxxProtocol
       com.apache.dubbo.registry.integration.YyyProtocol
    
       # 等号前面有多个值得情况
       zzz,default=com.apache.dubbo.registry.integration.CustomProtocol
    
    

以下解读,作者根据具体的某个扩展点去分析加载过程

Ⅰ. ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtion()
    1. 先判断出当前 type(Protocol.class) 是否有扩展类加载器,没有就创建一个
    1. 然后从缓存的 cachedAdaptiveInstance 适配实例Holder中查找,没有的话就创建一个适配类
      ExtensionLoader#getExtensionLoader
    1. 查找适配类:加载当前扩展点对应的全部扩展实现, META-INF/dubbo/ , META-INF/dubbo/internal/,META-INF/services/几个路径下,扩展点上一定要有@SPI 注解,并且注解的值只能有一个(默认扩展点
    1. 如果扩展实现上 带有 @Adaptive 注解,则当作默认的适配类扩展(只能定义一个扩展适配类实现)
    1. 否则尝试获取包装类扩展(带有扩展点接口的构造器),如果有则添加到 wrappers 集合中。

      此处可以得知(适配类即使是包装类的约束格式,也不会被当作包装类)

    1. 如果没有包装类,则直接获取无参构造器(此处目的是确保扩展实现可以被实例化)
    1. 判断 = 号前面的扩展实现名称,若为空(等号前面没有值),则取扩展实现类除去后缀的部分并且全部小写(例如:XxxProtocol 取 xxx,YyyProtocol 取 yyy)作为名称name
    1. 再将 name 根据逗号拆分,若扩展实现类上有 @Activate 激活注解,则取name[0]作为key,注解作为value,放入cachedActivates 缓存map中。然后边遍历 name ,依次添加到 cachedNames(key = 扩展实现class实例,value = name)中
    1. 最后都放入到 当前类型对应得扩展实现中 cachedClasses
    1. 如果缓存得适配类cachedAdaptiveClass 为空(表示 扩展实现类上有标注 @Adaptive),则需要创建适配类( Protocol 扩展点没有适配类 )
      ExtensionLoader#getAdaptiveExtension

      ExtensionLoader#createAdaptiveExtension
    1. 获取扩展点Protocol 所有方法,判断方法上是否存在 @Adaptive 注解,若不存在则直接抛异常,否则生成动态得适配类。如下:
package com.apache.dubbo.rpc;
import com.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.apache.dubbo.rpc.Protocol {
    public void destroy()
    {
        throw new UnsupportedOperationException(
            "method public abstract void com.apache.dubbo.rpc.Protocol.destroy() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!"
        );
    }
    public int getDefaultPort()
    {
        throw new UnsupportedOperationException(
            "method public abstract int com.apache.dubbo.rpc.Protocol.getDefaultPort() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!"
        );
    }
    public com.apache.dubbo.rpc.Exporter  export(com.apache.dubbo.rpc.Invoker arg0) throws com.apache.dubbo.rpc.RpcException
    {
        if(arg0 == null) throw new IllegalArgumentException("com.apache.dubbo.rpc.Invoker argument == null");
        if(arg0.getUrl() == null) throw new IllegalArgumentException(
            "com.apache.dubbo.rpc.Invoker argument getUrl() == null");
        com.apache.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.apache.dubbo.rpc.Protocol) name from url(" + url.toString() +
            ") use keys([protocol])");
        
        // 获取url中指定得协议,默认是 dubbo协议
        com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(
            com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        
        return extension.export(arg0); // 调用真正得协议扩展实现得export方法
    }

    public com.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.apache.dubbo.common.URL arg1) throws com.apache.dubbo.rpc.RpcException{
            if(arg1 == null) throw new IllegalArgumentException("url == null");
            com.apache.dubbo.common.URL url = arg1;
            
            // 获取url中指定得协议,默认是 dubbo协议
            String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            if(extName == null) throw new IllegalStateException(
                "Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() +
                ") use keys([protocol])");
        
            // 获取真实得协议扩展实现
            com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(
                com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        
            return extension.refer(arg0, arg1); // 调用真正得协议扩展实现得refer方法
        }
}  
    1. 创建完适配类之后,接着再获取 Compiler 得适配类,用于动态生成 Protocol 得适配类(其他动态生成得适配类也是用 Compiler 来生成得)。由于 Compiler存在适配类,即扩展点类加载器对应得缓存cachedAdaptiveClass不为空,直接返回。此处 cachedAdaptiveClass = com.apache.dubbo.common.compiler.support.AdaptiveCompiler
    1. 实例化并调用injectExtension 方法注入其他扩展实例(不适合框架实现得扩展适配类,因为动态生成得适配类基本上不会依赖其他扩展点):判断生成得适配类中 是否存在类似方法:1. set开头得 2. 只有一个参数类型 3. public访问修饰符,若有,则截取set 之后得首字母小姐得名称将其作为扩展名,然后通过 ExtensionFactory工厂获取此扩展名得实例,调用当前setXXX方法,叫之为IOC。由于 AdaptiveCompiler 只有一个set方法但是没有对应得 String.class类型得 defaultCompiler扩展点,所以此处不会进行注入:
      public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }
ExtensionLoader#injectExtension
    1. 接着调用适配扩展实现 AdaptiveCompilercompile方法,方法内会获取默认得扩展实现(此处是javassist扩展名)JavassistCompiler,然后调用其compile方法动态生成适配class实例。
Ⅱ. ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName)
上面生成得适配类中,有一步是获取真正调用扩展实现 :

com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(com.apache.dubbo.rpc.Protocol.class).getExtension(extName);

  • 首先还是获取 扩展点Protocol对应得扩展类加载器,接着调用 getExtension 方法获取指定得扩展实现,先查询是否已经缓存了相应得扩展实例,若没查到会调用 createExtension 方法去创建当前指定得扩展实现。
  • 调用createExtension 方法时,先从当前扩展点实现class实例缓存中查找,若是没有这个扩展名,说明传入得参数有问题,会直接抛出异常。然后再从对象缓存实例cachedClasses 中查找是否已经实例化了,没找到,实例化之后放入对象缓存EXTENSION_INSTANCES中。
  • 调用 injectExtension 方法注入其他得扩展点实现(IOC),同上面Step13
  • 获取当前扩展点得所有包装类cachedWrapperClasses ,循环调用包装类得有参构造器(参数就是扩展点类型)实例化,之后也会调用injectExtension 方法为包装类也注入其他得扩展点实现,并返回最后一个包装类得实例(每个Wrapper类都会包装一个之前得扩展实现 即: A -> B(A) -> C(B), 最后返回 C这个包装类)(AOP)。而当前扩展点 Protocol 有两个包装类实现:ProtocolFilterWrapperProtocolListenerWrapper

    所以,当获取适配扩展实现时:

    1. 若是动态生成得,则
      getAdaptiveExtion() -> getExtension("dubbo或者是 url.getProtocol()的值")
      -> injectExtension(T instance) (真正执行调用的扩展实现)
      -> injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))
      ( A -> B(A) -> C(B) 最后返回 C这个包装类)
    2. 若是定义了适配类实现,则直接走适配类的逻辑
ExtensionLoader#createExtension
Ⅲ. ExtensionLoader#addExtension(String name, Class clazz)
  • 首先加载所有的扩展类实现,若 clazz 没有实现 type扩展点,抛出异常。 (必须要实现扩展点,才可以添加)
  • clazz 是个接口,抛出异常。(扩展实现不能是接口,否则不能实例化)
  • clazz上没有带有 @Adaptive注解
    • 若 扩展名name 为空,抛出异常。 (扩展名不能为空)
    • 若 缓存扩展实现cachedClasses中已经存在当前扩展名,抛出异常。(扩展名不能重复)
    • 若以上两个条件都不满足,则将扩展名与扩展点建立映射关系 , 缓存到cachedNamescachedClasses中。
  • clazz上带有@Adaptive注解
    • 若适配类cachedAdaptiveClass 不为空,抛出异常。(扩展点的适配类只能有一个)
    • 为空的话,将当前 clazz作为扩展点的适配类,赋值给 cachedAdaptiveClass

    添加扩展实现的逻辑还是比较简单的。通过API的方式动态添加扩展实现,可以不通过配置文件的方式(挺实用的)

ExtensionLoader#addExtension
Ⅳ ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(url, key, group) :
  • 找到 ExtensionLoader的 getActivateExtension方法:
//  ExtensionLoader
public List getActivateExtension(URL url, String key, String group = consumer) {
    // 获取 URL 中指定 key对应的值
    // 以 ProtocolFilterWrappe r的 buildInvokerChain 方法为例(任何一个扩展点都可)
    // key = REFERENCE_FILTER_KEY,group = consumer
    // 可以进行如下改造 ( Filter自定义编排化):
    // 1. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-default");
    // 2. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-cache,actives");
    // 3. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-default,actives");
    // 你知道上面几种扩展方式,最后返回的 Filter扩展实现有哪些么 ?
    String value = url.getParameter(key);
    // value值用于控制激活扩展点是否加载,为空表示不指定激活扩展点名称,从所有的激活扩展实现中根据 @Activate 注解跟指定的组进行筛选,若是需要指定,定义的扩展点名称需要以 "," 分隔开
    return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}

//  ExtensionLoader
    public List getActivateExtension(URL url, String[] values, String group) {
        List exts = new ArrayList<>();
        List names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
        // 如果指定的扩展的名称不包括 -default(排除SPI中的实现)标识符,则从所有已经加载的激活扩展点中找到满足条件的,否则只加载指定的激活扩展点
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            getExtensionClasses();
            for (Map.Entry entry : cachedActivates.entrySet()) {// 遍历激活扩展点的所有实现
                String name = entry.getKey();
                Object activate = entry.getValue();

                String[] activateGroup, activateValue;

                if (activate instanceof Activate) {
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                    activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                } else {
                    continue;
                }
                if (isMatchGroup(group, activateGroup) // 如果当前需要加载的组在激活扩展点实现指定的组中才算匹配
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) { // URL中至少要有 activateValue[] 中的其中一个key,当前激活扩展点实现才会被使用
                    exts.add(getExtension(name));
                }
            }
            exts.sort(ActivateComparator.COMPARATOR); // 按照自然序排序
        }
        List usrs = new ArrayList<>();
        // 若names不为空,表示指定了扩展点名称,找到不是以排除标识符'-'开头并且不包括要排除(-name)的扩展点名称,获取其扩展实现
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                if (DEFAULT_KEY.equals(name)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    usrs.add(getExtension(name));
                }
            }
        }
        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }
        return exts;
    }
  • 上述就是Dubbo源码中经常出现的获取扩展实现的代码,了解了其原理之后,后面我们解读源码时,会更清晰自然了。
  1. ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
  2. 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
  3. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!

你可能感兴趣的:(Dubbo设计之ExtensionLoader)