发现别人写的很好了,这只是自己记录一下。摘抄一下,以后遇到问题,再更新
Wrapper的规范
Wrapper 机制不是通过注解实现的,而是通过一套 Wrapper 规范实现的。
Wrapper 类在定义时需要遵循如下规范:
- 该类要实现 SPI 接口
- 该类中要有 SPI 接口的引用
- 该类中必须含有一个含参的构造方法且参数只能有一个类型为SPI借口
- 在接口实现方法中要调用 SPI 接口引用对象的相应方法
- 该类名称以 Wrapper 结尾
1、
@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
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)代理模式
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)责任链模式
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
----pay before... pay before... 使用支付宝支付 pay after... -----pay after...
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
复制代码
这时会动态生成并加载一个 protocol$Adaptive
类,然后这时候我们调对应有 @Adaptive
修饰的方法的时候传入 URL
就可以根据对应的字段获取对应的扩展。
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
方法进行加载。具体顺序如下:
- 调用
getExtensionClasses
获取扩展方法 - 调用
loadExtensionClasses
加载扩展方法 - 调用
loadDirectory
按照目录资源加载 - 调用
loadResource
按照资源路径加载 - 调用
loadClass
方法加载单个类资源
loadClass
方法的作用主要有几个:
- 类是否被
@Adaptive
修饰 - 通过
isWrapper
方法判断是否是包装类 - 缓存
@Activate
修饰的信息
我们这一节主要讨论的是 Wrapper
。我们看看 Wrapper
的相关代码。首先是通过判断是否是扩展类
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
。那么我们查看这个文件,我们会发现有很多相关的定义:
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
就只有三个。它们分别是
-
ProtocolListenerWrapper
过滤链调用链条包装器 -
QosProtocolWrapper
协议监听器包装器 -
ProtocolFilterWrapper
在线运维服务包装器
缓存好了之后,后面的处理方法就是,加载一个未曾实例化的类的时候,会调用 createExtension
方法,就是创建扩展进行实例化对象的时候,我们取 cachedWrapperClasses
进行包装,具体代码如下:
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
,接下来的步骤和上面一样。
到了实例化完毕后,最后的 instance
是 ProtocolFilterWrapper
。这样对应的 Dubbo
的 Holder
持有的对象就是 ProtocolFilterWrapper
。而这样我们可以做什么呢?其实我们可以发现的是,这是一种嵌套的过滤链,你可以理解为这是实现了责任链的调用,也可以是理解为这是 AOP
的某种实现方式(因为实际上,Spring
的 IOC
也是通过 beanName
查找 bean
,但是可以通过替换来实现 AOP
的真实实例),反正只要理解了都可以!
接下来就是调用的时候了。其实为什么判断是否 Wrapper
的标准是构造器的形参呢?其实就是为了调用的方式与实际上该调用的类无差异。还是举 Protocol
作为例子。当我们要调用 DubboProtocol
的 export
方法的时候,我们实际上调用的是 ProtocolFilterWrapper
的 export
方法,这样子在表面上我们调用的是 Protocol
的 export
方法,这样是不是充分利用了接口的作用?
接下来我们试图解析一下上面例子的调用过程。首先看 ProtocolFilterWrapper#export
。
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
。
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
持有的 protocol
是 ProtocolListenerWrapper
。
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)));
}
复制代码
上面是由两步进行:
- 通过
ExtensionLoader
获取ExporterListener
的插件,然后传入url
调用特定扩展,得到返回结果信息 -
ProtocolListenerWrapper
持有的实际上是DubboProtocol
,所以将DubboProtocol
方法调用的结果和第一步得到的结果信息封装成ListenerExporterWrapper
摘抄链接:https://www.cnblogs.com/caoxb/p/13140345.html
摘抄链接:https://juejin.cn/post/6901485122701230094