Dubbo源码分析:URL总线和ExtensionLoader-框架基石

撸了今年阿里、腾讯和美团的面试,我有一个重要发现…….

作者:backend

出处:https://blog.csdn.net/u010013573/article/category/8462451


概述

ExtensionLoader类为Dubbo框架SPI的实现类,相当于JDK的ServiceLoader,也实现了Spring的IOC功能,通过ExtensionLoader配合URL完成对应类实现的加载,是Dubbo框架高度可拓展性实现的基础。以下各个规则和实现都是在ExtensionLoader提供实现,如从META-INF目录获取SPI的类声明,Adaptive注解配合URL参数或protocol,自适应获取SPI接口实现类等。

201907191001_1.png

自定义SPI

  1. 所有需要通过ExtensionLoader解析、设置属性,自适应获取实现类的接口,均需要增加@SPI声明,如下为:Protocol接口:
    201907191001_2.png
  2. 增加了根据实例别名的概念,如:
    dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol,默认java spi则只在META-INF/services目录,包含接口全名作为文件,文件内容为各个实现类全名列表;
  3. 增加存放spi定义文件的目录,包括:META-INF/dubbo,META-INF/dubbo/internal和META-INF/services,而不只是MATE-INF/services。
    1,2 如图:
    201907191001_3.png

Adaptive与Activate注解

  • @Adaptive为根据URL参数,在运行时动态决定选择哪种接口实现,@Adaptive注册可以用在实现类上,但是只能放在接口的其中一个实现类上。更多的时候是将@Adaptive注解放在接口方法上,表示这些方法可以在运行时决定使用哪个spi实现类。如下为注册工厂的接口:
    201907191001_4.png
    在getRegistry方法上使用@Adaptive({“protocol”})表示:根据URL的protocol参数的值,如:zookeeper://xxx,决定使用以下哪个注册工厂实现:
    201907191001_5.png
    动态选择接口实现的核心源码如下:
    201907191001_6.png
    如上图:为从method获取Adaptive注解,然后如下图:获取@Adaptive注解的value数组,如果没有则根据接口名设置value。
    201907191001_7.png
    如下图:
  • 根据value的值,决定是从url的parameter获取spi实现,还是根据URL的protocol选择spi实现。即:如果value值为protocol则根据url的protocol,否则根据url中parameter选择。
  • 通过value产生了getNameCode的具体值,根据getNameCode得出了当前选择的spi实现类的别名extName,最后通过getExtensionLoader(接口.class).getExtension(extName)加载最终调用的实现类。
    201907191001_8.png
  • Activate为根据URL参数,动态决定是否激活某个类,如ProtocolFilter接口的实现类就是根据Activate规则,决定激活哪些过滤器。

Wrapper包装器

  • 定义:某个接口的包装类,实现该接口并且只包含一个以该接口作为唯一参数的构造函数的类
  • 作用:用于增强、装饰该接口实际提供功能的类的实例,即该接口的具体实现,多个包装器类无序地包装到该实例上。如Protocol接口包括两个wrapper类分别为:
    201907191001_9.png

URL总线

dubbo为以URL为主线的一个框架,即根据URL中的相关参数,adaptive自适应选择对应的spi实现类,核心实现为:
201907191001_10.png
201907191001_11.png
201907191001_12.png
基本逻辑为:
根据构造函数传入的type:
201907191001_13.png
依次查看type的每个方法的每个参数,如果存在参数类型为URL则就找到了;如果没有,则继续查看参数所属类的每个方法,是否存在“以get开头,不存在参数,返回类型为URL”的方法,有则找到了。

案例:ServiceConfig服务注册

在dubbo-bootstrap中,遍历ServiceConfig列表,开始提供者的服务导出注册。
ServiceConfig的export导出提供者的方法到注册中心,从源码实现来看是通过ExtensionLoader以下声明了一个protocol:
201907191001_14.png
实际调用的是RegistryProtocol,而能判断最终调用的是RegistryProtocol,是因为url为:registry://xxx,具体为invoker的url为registry://xxx,即url.getProtocol()返回为registry。
Protocol接口为方法export(Invoker invokder)对应的Invoker接口继承于Node,Node接口包含URL getUrl()方法。
201907191001_15.png
dubbo生成的adative类:

    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.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
            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();
            // 此处url.getProtocol不为null,返回registry
            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);
        }
        public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
            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);
        }
    }

参考:
https://blog.csdn.net/yangxiaobo118/article/details/80696830

赞(0) 打赏

如未加特殊说明,此网站文章均为原创,转载必须注明出处。Java 技术驿站 » Dubbo源码分析:URL总线和ExtensionLoader-框架基石
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

关注【Java 技术驿站】公众号,每天早上 8:10 为你推送一篇技术文章

扫描二维码关注我!


关注【Java 技术驿站】公众号 回复 “VIP”,获取 VIP 地址永久关闭弹出窗口

免费获取资源

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏