我是靠谱客的博主 自由石头,这篇文章主要介绍Dubbo Wrapper 解析,现在分享给大家,希望可以做个参考。

发现别人写的很好了,这只是自己记录一下。摘抄一下,以后遇到问题,再更新

Wrapper的规范

Wrapper 机制不是通过注解实现的,而是通过一套 Wrapper 规范实现的。

Wrapper 类在定义时需要遵循如下规范:

  • 该类要实现 SPI 接口
  • 该类中要有 SPI 接口的引用
  • 该类中必须含有一个含参的构造方法且参数只能有一个类型为SPI借口
  • 在接口实现方法中要调用 SPI 接口引用对象的相应方法
  • 该类名称以 Wrapper 结尾

1、

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@SPI("ali") // 默认的值支付宝支付 public interface Pay { // 接口的方法需要添加这个注解,在测试代码中,参数至少要有一个URL类型的参数 @Adaptive({"paytype"}) // 付款方式 void pay(URL url); } public class AliPay implements Pay { @Override public void pay(URL url) { System.out.println("使用支付宝支付"); } } public class WechatPay implements Pay { @Override public void pay(URL url) { System.out.println("使用微信支付"); } } 在/dubbo-common/src/main/resources/META-INF/services/com.test.Pay文件下添加内容如下: wechat = com.test.WechatPay ali = com.test.AliPay
复制代码
1
2
3
4
5
6
7
8
public static void main(String[] args) { ExtensionLoader<Pay> loader = ExtensionLoader.getExtensionLoader(Pay.class); Pay pay = loader.getAdaptiveExtension(); pay.pay(URL.valueOf("http://localhost:9999/xxx")); // 使用支付宝支付 pay.pay(URL.valueOf("http://localhost:9999/xxx?paytype=wechat")); // 使用微信支付 }

上述示例是原Adaptive使用示例,在使用Wrapper之后:

  • 首先要添加一个Wrapper类
  • 并在/dubbo-common/src/main/resources/META-INF/services/com.test.Pay文件下追加“xxx = com.test.PayWrapper1”

(1)代理模式

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
public class PayWrapper1 implements Pay { Pay pay; public PayWrapper1(Pay pay) { this.pay = pay; } @Override public void pay(URL url) { System.out.println("pay before..."); pay.pay(url); System.out.println("pay after..."); } }

pay before...
使用支付宝支付
pay after...
pay before...
使用微信支付
pay after...

(2)责任链模式

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
public class PayWrapper2 implements Pay { Pay pay; public PayWrapper2(Pay pay) { this.pay = pay; } @Override public void pay(URL url) { System.out.println("-----pay before..."); pay.pay(url); System.out.println("-----pay after..."); } }

并追加xxx2 = com.test.PayWrapper2

复制代码
1
2
3
4
5
----pay before... pay before... 使用支付宝支付 pay after... -----pay after...
复制代码
1
2
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); 复制代码

这时会动态生成并加载一个 protocol$Adaptive 类,然后这时候我们调对应有 @Adaptive 修饰的方法的时候传入 URL 就可以根据对应的字段获取对应的扩展。

复制代码
1
2
3
4
5
6
7
com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = ((url.getProtocol() == null) ? "dubbo" : url.getProtocol()); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class) .getExtension(extName); 复制代码

但是有时候,有些调用的并不是直接调用目标扩展的方法,在它们调用的前面可能还会有像 Fitler(过滤器) 这样的类来先执行,所以在 Dubbo 里面这个机制也是非常重要的!

准备好了吗?Let's Go~

首先我们回忆一下,在 Dubbo 加载一个未曾加载的类的时候,会通过 getExtensionClasses 方法进行加载。具体顺序如下:

  1. 调用 getExtensionClasses 获取扩展方法
  2. 调用 loadExtensionClasses 加载扩展方法
  3. 调用 loadDirectory 按照目录资源加载
  4. 调用 loadResource 按照资源路径加载
  5. 调用 loadClass 方法加载单个类资源

loadClass 方法的作用主要有几个:

  1. 类是否被 @Adaptive 修饰
  2. 通过 isWrapper 方法判断是否是包装类
  3. 缓存 @Activate 修饰的信息

我们这一节主要讨论的是 Wrapper。我们看看 Wrapper 的相关代码。首先是通过判断是否是扩展类

复制代码
1
2
3
4
5
6
7
8
9
private boolean isWrapperClass(Class<?> clazz) { try { clazz.getConstructor(type); return true; } catch (NoSuchMethodException e) { return false; } } 复制代码

上面的判断方式就是当前的的 clazz 是否有一个构造器是其父类,如果有说明它是包装类。然后将其加入到 ExtensionLoader 的成员属性 cachedWrapperClasses 当中去。这一步主要是为了检测出哪些类是包装类。

举个例子, 在 Dubbo 中最经典的是 Protocol。我们首先去看 Protocol 的扩展类有哪些。怎么看?还记得 Dubbo SPI 的扩展定义文件是根据接口的包路径来定义文件名的。那么 Protocol 的包名全路径就是 com.alibaba.dubbo.rpc.Protocol。那么我们查看这个文件,我们会发现有很多相关的定义:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol com.alibaba.dubbo.rpc.protocol.http.HttpProtocol com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol registry=com.alibaba.dubbo.registry.integration.RegistryProtocol qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper 复制代码

上面虽然很多类,但是最终鉴定为 WrapperClass 就只有三个。它们分别是

  1. ProtocolListenerWrapper 过滤链调用链条包装器
  2. QosProtocolWrapper 协议监听器包装器
  3. ProtocolFilterWrapper 在线运维服务包装器

缓存好了之后,后面的处理方法就是,加载一个未曾实例化的类的时候,会调用 createExtension 方法,就是创建扩展进行实例化对象的时候,我们取 cachedWrapperClasses 进行包装,具体代码如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private T createExtension(String name) { Class<?> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); //从成员属性获取对应的 cachedWrapperClasses Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { //循环 for (Class<?> wrapperClass : wrapperClasses) { //实例化,同时进行注入 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { // throw Exception... } } 复制代码

上面的方法重点代码已被注释。我们依旧使用例子进行解读。假设我们准备实例化 DubboProtocol,那么当前的 instance=DubboProtocol。这时候,ProtocolListenerWrapper 会将 DubboProtocol 作为构造器参数进行实例(injectExtension 主要是负责 set* 前缀的方法注入),然后将 ProtocolListenerWrapper 赋值给 instance;接下来是实例化 QosProtocolWrapper,依旧是将 instance=ProtocolListenerWrapper 作为构造器参数进行实例,,然后将 QosProtocolWrapper 赋值给 instance;最后就是 ProtocolFilterWrapper,接下来的步骤和上面一样。

到了实例化完毕后,最后的 instanceProtocolFilterWrapper。这样对应的 DubboHolder 持有的对象就是 ProtocolFilterWrapper。而这样我们可以做什么呢?其实我们可以发现的是,这是一种嵌套的过滤链,你可以理解为这是实现了责任链的调用,也可以是理解为这是 AOP 的某种实现方式(因为实际上,SpringIOC 也是通过 beanName 查找 bean,但是可以通过替换来实现 AOP 的真实实例),反正只要理解了都可以!

接下来就是调用的时候了。其实为什么判断是否 Wrapper 的标准是构造器的形参呢?其实就是为了调用的方式与实际上该调用的类无差异。还是举 Protocol 作为例子。当我们要调用 DubboProtocolexport 方法的时候,我们实际上调用的是 ProtocolFilterWrapperexport 方法,这样子在表面上我们调用的是 Protocolexport 方法,这样是不是充分利用了接口的作用?

接下来我们试图解析一下上面例子的调用过程。首先看 ProtocolFilterWrapper#export

复制代码
1
2
3
4
5
6
7
8
9
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { //通过 url 的协议查看是不是 Registry if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { return protocol.export(invoker); } //建立 invoker 调用链,然后传递给下一个 return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER)); } 复制代码

根据上面的流程,我们知道 ProtocolFilterWrapper 持有了 QosProtocolWrapper。然后我们继续看 QosProtocolWrapper#export

复制代码
1
2
3
4
5
6
7
8
9
10
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { //通过 url 的协议查看是不是 Registry if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { startQosServer(invoker.getUrl()); return protocol.export(invoker); } //直接调用持有对象 return protocol.export(invoker); } 复制代码

我们可以发现的是,QosProtocolWrapper 是在 registry 协议的时候才会有正式的处理代码,实际上就是开启一个 QosServer 服务。

同理,QosProtocolWrapper 持有的 protocolProtocolListenerWrapper

复制代码
1
2
3
4
5
6
7
8
9
10
11
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { 通过 url 的协议查看是不是 Registry if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { return protocol.export(invoker); } //两步进行 return new ListenerExporterWrapper<T>(protocol.export(invoker), Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class) .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY))); } 复制代码

上面是由两步进行:

  1. 通过 ExtensionLoader 获取 ExporterListener 的插件,然后传入 url 调用特定扩展,得到返回结果信息
  2. ProtocolListenerWrapper 持有的实际上是 DubboProtocol,所以将 DubboProtocol 方法调用的结果和第一步得到的结果信息封装成 ListenerExporterWrapper


摘抄链接:https://www.cnblogs.com/caoxb/p/13140345.html
摘抄链接:https://juejin.cn/post/6901485122701230094

最后

以上就是自由石头最近收集整理的关于Dubbo Wrapper 解析的全部内容,更多相关Dubbo内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(62)

评论列表共有 0 条评论

立即
投稿
返回
顶部