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

原创
03/27 17:09
阅读数 417

开篇

上一篇讲到了 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);
    }
}
展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部