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

开篇

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

正文

大纲apache

  • 扩展点自适应介绍
  • @Adaptive 注解使用方法segmentfault

    • 做用在类上
    • 做用在方法上

1. 扩展点自适应

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

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

扩展点方法调用会有 URL 参数(或是参数有URL成员)这样依赖的扩展点也能够从URL拿到配置信息,全部的扩展点本身定好配置的 Key 后,配置信息从 URL 上从最外层传入。URL在配置传递上便是一条总线。浏览器

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

划重点:.net

  • 扩展方法中有 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);
    }
}
相关文章
相关标签/搜索