美文网首页
Dubbo SPI 使用方法(二)- 扩展点自适应

Dubbo SPI 使用方法(二)- 扩展点自适应

作者: nimo10050 | 来源:发表于2020-03-29 10:35 被阅读0次

    开篇

    上一篇讲到了 Dubbo SPI 使用方法(1) - 扩展点自动包装
    本文接着讲 Dubbo SPI - 扩展点自适应。

    正文

    大纲

    • 扩展点自适应介绍
    • @Adaptive 注解使用方法
      • 作用在类上
      • 作用在方法上

    1. 扩展点自适应

    ExtensionLoader 注入的依赖扩展点是一个 Adaptive 实例,直到扩展点方法执行时才决定调用是哪一个扩展点实现。

    Dubbo 使用 URL 对象(包含了Key-Value)传递配置信息。

    扩展点方法调用会有 URL 参数(或是参数有URL成员)这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的 Key 后,配置信息从 URL 上从最外层传入。URL在配置传递上即是一条总线。

    上面摘自官网的一段介绍。

    划重点:

    • 扩展方法中有 URL 参数

      也可以是包含 URL 成员的参数

    • 直到扩展点方法执行时,才决定调用哪个扩展点实现

      扩展点自动包装的区别

    • 通过 URL 传递配置信息

      通过 URL 中的参数,决定调用哪个扩展类实现

    如果还是不好理解,就继续看下面的案例。

    2. @Adaptive 注解

    要想实现 扩展点自适应,需要借助 @Adaptive注解,该注解可以作用在两个地方:

    • 扩展实现类上

    在类上,表示该类是一个扩展类,不需要生成代理直接用即可;

    • 扩展接口方法上

    在方法上则表示该方法需生成代理, 此时就需要用到上面提到的 URL 参数

    2.1 作用在扩展实现类上

    这个相对比较简单,没什么特别的地方,上面也有提到,当 @Adaptive作用在类上,就表示该类是一个扩展类。

    再说的简单点就是:

    如果作用在方法会帮我们在运行时动态生成一个 Adaptive 实例,

    如果作用在类上就相当于自己定义了一个现成的。

    // 定义一个扩展接口
    interface HelloService {
        void sayHello();
    }
    
    // 定义一个自适应扩展类
    @Adaptive
    class HelloServiceAdaptive implements HelloSerivce{
        void sayHello(){
            // doSomthing
        }
    }
    
    ExtensionLoader<HelloService> extensionLoader =
            ExtensionLoader.getExtensionLoader(HelloService.class);
            
    // 获取 Adaptive 实例        
    HelloService helloservice = extensionLoader.getAdaptiveExtension()
    
    2.2 作用在扩展接口方法上

    当 @Adaptive 注解作用在扩展接口方法上时,方法中需要传入一个 URL 参数,或者包装有 URL 的参数时,会通过动态编译获得一个 Adaptive 实例

    使用如下:

    1. 定义一个扩展接口:
    interface Protocol {
        // 关键字 2 : Key
        // 这里定义一个 Key,因为是数组,所以可以传多个
        // Key 的作用会在后面看到
        @Adaptive({"key1"})
        void export(URL url)
    }
    
    1. 定义多个扩展接口的实现类

    篇幅原因,只贴出一个 DubboProtocol

    class DubboProtocol implements Prototol {
        
        void export(URL url) {
            print("我是 dubbo protol")
        }
        
    }
    
    1. 配置 META-INF/dubbo/com.xx.Prototol 文件
    dubbo=com.xx.Dubboprotocol
    
    1. 程序入口
    
    Protol protol = extensionLoader.getAdaptiveExtension()
    
    
    // 把步骤一 中的 Key 作为 “键” 传入 map 中,
    // value 对应步骤三定义的:扩展接口的实现的名称
    // 如果定义多个 key,这个也穿多个
    HashMap<String, String> params = new HashMap<>();
    params.put("key2", "dubbo");
    
    // 定义一个 URL,
    URL url = new URL("dubbo", "localhost", 8080, params);
    
    protocol.export(url);
    
    1. 动态生成Adaptive 实例

    程序运行时,会经过动态编译过程生成 Protocal 对应的 Adaptive 实例, 即 Protocol$Adaptive

    具体来讲:就是在程序运行过程中,根据条件,通过拼接字符串的形式生成 java 源码,然后进行编译获得对应的实例

    调试 Dubbo 源码时,修改日志级别为 DEBUG ,控制台会打印出源码

    (文末贴出了 Dubbo 动态编译出来的 Protocol$Adaptive):

    下面是当 @Adaptive 注解作用在 Protocol 扩展接口上 (自定义的一个接口,不是 Dubbo 中那个),运行时产生的 Adaptive 实例对应的源码。

    class Protocol$Adaptive implements Protocol {
    
        // 这里全是伪代码
        void export(URL url) {
            // 获取 url 的参数, 比如:dubbo
            // 如果 key1 不存在,会从其他 Key(key2,keyn..)中获取
            String extName = url.get("key1")
            // 获取具体扩展实现类
            DubboProtocol protocol = getExtensition(extName);
            // 调用 export 方法
            protocol.export(url)
        }
        
    }
    
    

    总结

    扩展点自适应就是利用 @Adaptive 注解,来获取对应扩展接口的 Adaptive 实例。

    如果注解作用在类上,那么这个类就会被直接标记成一个 Adaptive;

    如果注解作用在方法上,会通过动态编译技术,动态生成一个只包含该方法的 Adaptive;

    两者有什么区别呢?

    举个不恰当的例子;

    有一个需求是浏览器发起一个请求到后台,后台会跳转到另一个 URL;
    
    前者更像是我已经明确知道要跳转的 URL 是什么了,我直接定死在后台代码;
    
    后者则是我不知道要跳转到哪,跳转 URL 需要浏览器传过来,我再根据这个参数去跳转。
    

    下篇文章会通过 Dubbo 的 Protocol 扩展点来举例说明。

    附录

    Dubbo 动态编译生成的 Protocol$Adaptive

    package com.nimo.dubbospi.protocol;
    
    import org.apache.dubbo.common.extension.ExtensionLoader;
    
    public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
        public void destroy() {
            throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
        }
    
        public int getDefaultPort() {
            throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
        }
    
        public java.util.List getServers() {
            throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
        }
    
        public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
            if (arg1 == null) throw new IllegalArgumentException("url == null");
            org.apache.dubbo.common.URL url = arg1;
            String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            if (extName == null)
                throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
            org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
            return extension.refer(arg0, arg1);
        }
    
        public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
            if (arg0 == null)
                throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
            if (arg0.getUrl() == null)
                throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
            org.apache.dubbo.common.URL url = arg0.getUrl();
            String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            if (extName == null)
                throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
            org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
            return extension.export(arg0);
        }
    }
    

    相关文章

      网友评论

          本文标题:Dubbo SPI 使用方法(二)- 扩展点自适应

          本文链接:https://www.haomeiwen.com/subject/rcyyuhtx.html