DubboSPI使用方法(二)扩展点自适应

编程

开篇

上一篇讲到了 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);

}

}

以上是 DubboSPI使用方法(二)扩展点自适应 的全部内容, 来源链接: utcz.com/z/514801.html

回到顶部