目录
总结
Protocol
@Adaptive修饰方法来指明需要动态生成代码类来增强指定方法
服务发布过程#export
#loadRegistries方法里将协议从‘dubbo’ 改为‘registry’
#doExportUrlsFor1Protocol方法里执行Protocol$Adaptive#export服务发布
执行进入Protocol$Adaptive#export
执行进入RegistryProtocol#export方法,最终回到DubboProtocol
总结
- @SPI:注解可以指定名称,用来匹配接口的实现类。在接口配置文件里不仅有最终要使用的target,还把有【该接口类型作为入参的构造器】的实现类当做它的wrapper,在创建target对象时裹在外层。
- @Adaptive: 运行时对被该注解修饰了方法的类,用javassist生成并编译增强代码。只有被修饰的方法才被增强!
所以Protocol实际上是Protocol$Adaptive,在创建实例时外层用wrapper包裹target。最重要的是ProtocolFilterWrapper, 它对DubboProtocol订阅和发布过程里的Invoker创建过滤器链,eg. MonitorFilter、TPSLimiter、ExecuteLimitFilter...
Protocol
Protocol提供了服务发布#export 及 服务消费订阅#refer两个操作入口。 技术原理上是相通的。
本文以服务发布Protocol#export的过程实现为例,来讲述dubbo对 @SPI(需要载入的接口实现类文件配置化并通过名称指定使用)和@Adaptive(动态创建并编译增强代码)的逻辑。
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
//服务提供者发布服务, 这里的invoker是serviceImpl的封装代理
@Adaptive
Exporter export(Invoker invoker) throws RpcException;
// 消费者的订阅服务, 这里会绑定通信Client
@Adaptive
Invoker refer(Class type, URL url) throws RpcException;
void destroy();
}
图里有2个重要的注解:
- 类定义上的@SPI("dubbo") : 结合dubbo对spi的实现, 由文件配置的方式来指定加载接口的实现实体类。 匹配命名指定使用实现类DubboProtocol;
- 在#export、#refer方法上使用的@Adaptive : dubbo对有该注解修饰了方法的类动态生成增强代码类,真正被增强的只有被修饰了的方法!
在Provider发布服务的过程源码里,ServiceBean的父类ServiceConfig里有个静态Protocol对象:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
配置文件META-INFdubbointernalcom.alibaba.dubbo.rpc.Protocol:
按常规逻辑这里指定的就该是DubboProtocol, 但实际上确如下图所示:Protocol$Adaptive
这也就引出了本文的主题:dubbo会对@Adaptive修饰的方法体动态生成增强代码。具体如下:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
// 根据extName找到具体适应类,然后调用方法
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
// 根据extName找到具体适应类,然后调用方法
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
@Adaptive修饰方法来指明需要动态生成代码类来增强指定方法
当执行进入 ExtensionLoader#getAdaptiveExtension() -> #createAdaptiveExtension 动态创建并编译代码的过程里:首先会从配置文件中装载实现Protocol接口的实现类。
判定: [有入参是Protocol.class的构造器] 的实现类将被集中到“cachedWrapperClasses”。 用来后续对指定真正使用的Protocol实例进行封装!
// 构造函数的入参是指定的接口
private boolean isWrapperClass(Class> clazz) {
try {
clazz.getConstructor(type);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
ExtensionLoader#createAdaptiveExtensionClass使用 javassist 字节码编辑技术动态创建并编译了 Protocol$Adaptive ! 在文章开头有贴它源码。
服务发布过程#export
回到入口处: ServiceBean#export() -> #doExport() ->#doExportUrls
private void doExportUrls() {
List registryURLs = loadRegistries(true); //判定如果有注册中心,重写url协议为register
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);//发布服务
}
}
#loadRegistries方法里将协议从‘dubbo’ 改为‘registry’
原始的URL信息配置如下图:
经过处理后得到registry协议头的URL:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=trusts-core&dubbo=2.6.2&logger=slf4j&pid=27468®istry=zookeeper×tamp=1654680258624
#doExportUrlsFor1Protocol方法里执行Protocol$Adaptive#export服务发布
这里会根据配置参数组装dubbo协议头的URL,并在之后创建Invoker代理时,将它拼入registry协议头URL末尾的export部分!
dubbo://139.139.37.184:20880/cn.utrust.trusts.interf.BuyBackInterface?anyhost=true&application=trusts-core&bind.ip=139.139.37.184&bind.port=20880&dubbo=2.6.2&generic=false&group=cmis-utrusts&interface=cn.utrust.trusts.interf.BuyBackInterface&logger=slf4j&methods=buyBackSendRepayToYcloans,buyBack&pid=25416&retries=-1&revision=1.0&side=provider&threads=1024&timeout=180000×tamp=1654683368355&version=1.0
转换成了:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=trusts-core&dubbo=2.6.2&export=dubbo%3A%2F%2F139.139.37.184%3A20880%2Fcn.utrust.trusts.interf.BuyBackInterface%3Fanyhost%3Dtrue%26application%3Dtrusts-core%26bind.ip%3D139.139.37.184%26bind.port%3D20880%26dubbo%3D2.6.2%26generic%3Dfalse%26group%3Dcmis-utrusts%26interface%3Dcn.utrust.trusts.interf.BuyBackInterface%26logger%3Dslf4j%26methods%3DbuyBackSendRepayToYcloans%2CbuyBack%26pid%3D25416%26retries%3D-1%26revision%3D1.0%26side%3Dprovider%26threads%3D1024%26timeout%3D180000%26timestamp%3D1654683368355%26version%3D1.0&logger=slf4j&pid=25416®istry=zookeeper×tamp=1654683267651
执行进入Protocol$Adaptive#export
// Protocol$Adaptive源码
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); // 如果url的protocol为空, 默认走 dubbo
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
// 根据extName找到具体适应类,然后调用方法
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
从上述Protocol$Adaptive源码可知, 这里关键的是: url#getProtocol()决定了下一个要执行的Protocol接口实现类!
所以此时使用ExtensionLoader#getExtension('registry')去拿registryProtocol的实例:如果没有历史缓存,则#createExtension创建新的实例。
从上图中可知道: 新建时会使用多个wrapperClass来封装真实对象。这几个wrapperClass就是在读配置文件时归集而来[有Protocol为入参构造器]的实现类。
- ProtocolFilterWrapper : 对非‘registry’协议头的URL在export、refer时给Invoker绑定使用配置的过滤器链FilterChain!Filter通过@Activate来指定支持的group: provider consumer 及在URL特别指定匹配相应Filter的操作指令:executes、tps、tracing...。
- QosProtocolWrapper : Quality of Service,服务质量。 用来开启一个默认22222端口的服务,用来后台telnet上来online和offline service
- ProtocolListenerWrapper : 通过 ExporterListener、InvokerListener 接口对服务的订阅和消费进行监听。默认看起来没具体实现。
这里还需要关注一下#injectExtension方法, 通过反射setter方式,又把AdaptiveExtensionFactory里缓存的Protocol$Adaptive注入回RegistryProtocol里!
执行进入RegistryProtocol#export方法,最终回到DubboProtocol
执行到该方法后,会拿到源头的dubbo协议头URL,从下图中可以看出,又要回到Protocol$Adaptive#export的执行过程里了。只是这次就要选择DubboProtocol#export



