上一篇讲到了 Dubbo SPI 使用方法(1) - 扩展点自动包装 。
本文接着讲 Dubbo SPI - 扩展点自适应。
大纲
@Adaptive 注解使用方法
ExtensionLoader 注入的依赖扩展点是一个 Adaptive 实例,直到扩展点方法执行时才决定调用是哪一个扩展点实现。
Dubbo 使用 URL 对象(包含了Key-Value)传递配置信息。
扩展点方法调用会有 URL 参数(或是参数有URL成员)这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的 Key 后,配置信息从 URL 上从最外层传入。URL在配置传递上即是一条总线。
上面摘自官网的一段介绍。
扩展方法中有 URL 参数
也可以是包含 URL 成员的参数
跟 扩展点自动包装 的区别
通过 URL 传递配置信息
通过 URL 中的参数,决定调用哪个扩展类实现
如果还是不好理解,就继续看下面的案例。
要想实现 扩展点自适应
,需要借助 @Adaptive
注解,该注解可以作用在两个地方:
在类上,表示该类是一个扩展类,不需要生成代理直接用即可;
在方法上则表示该方法需生成代理, 此时就需要用到上面提到的 URL 参数
这个相对比较简单,没什么特别的地方,上面也有提到,当 @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()
当 @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") } }
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);
程序运行时,会经过动态编译过程生成 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); } }