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 实例
使用如下:
- 定义一个扩展接口:
interface Protocol { // 关键字 2 : Key
// 这里定义一个 Key,因为是数组,所以可以传多个
// Key 的作用会在后面看到
@Adaptive({"key1"})
void export(URL url)
}
- 定义多个扩展接口的实现类
篇幅原因,只贴出一个 DubboProtocol
class DubboProtocol implements Prototol { void export(URL url) {
print("我是 dubbo protol")
}
}
- 配置 META-INF/dubbo/com.xx.Prototol 文件
dubbo=com.xx.Dubboprotocol
- 程序入口
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);
- 动态生成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