Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
最近这一周在看dubbo框架的源码。之前的公司用的dubbo框架做分布式开发,所以我对 dubbo的基本原理还是比较清楚,加上dubbo源码不算太复杂,所以dubbo的源码大体脉络很容易就梳理清楚了。在阅读过程中,遇到最大的问题就是有些地方不知道是调用的哪个类。例如在进行服务暴露的过程中会用到protocol,这个protocol的具体实现类在哪呢?很困惑,如果这个地方搞不明白,那Exporter的的创建,就更搞不明白了。所以我决定一定要写一篇文章,把dubbo的spi机制分析明白。工欲善其事,必先利其器。
JDK的SPI机制:
SPI的全名是Service Provider Interface,这个概念最初是针对于厂商或者插件的。 具体请看我上一篇文章: juejin.cn/post/707457…
JDK的SPI机制的缺点
1.配置文件中只是简单地列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确地引用它们。
2.扩展如果依赖其他的扩展,做不到自动注入和装配。
Dubbo的SPI
我们以创建Exporter为例,讲解下SPI创建实现类的流程:
1.ServiceConfig类中对protocol的声明:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class)
.getAdaptiveExtension();
先看下getExtensionLoader()方法,做了什么:
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
//类不包含SPI注解会报错
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
//创建ExtensionLoader并返回
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
getAdaptiveExtension()方法会使用JavassistCompiler创建出ProtocolAdaptive类是动态的构造出源码字符串之后,再经过JavassistCompiler编译后创建出来的。
Protocol$Adaptive类的源码如下:
package org.apache.dubbo.rpc;
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 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);
}
}
2.记录实现类的小本本
dubbo的SPI配置文件,是在/dubbo/internal目录下。我们找到Protocol接口的配置文件如下图:
dubbo的配置文件中,每个实现类前面都定义了一个key,所以我们要使用任何一个实现类,都可以通过key,精准地定位到。
3.invoker的本地暴露
private void exportLocal(URL url) {
//在将invoker进行本地暴露的时候,url中设定的协议为injvm
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
//此次的protocol为Protocol$Adaptive实例
Exporter<?> exporter = protocol.export(
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
上述代码中LOCAL_PROTOCOL的值为injvm,配置文件中injvm对应的Class为
org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol,但是debug的时候发现exporter为
ProtocolFilterWrapper实例。这是怎么回事呢?
我们看下Protocol$Adaptive中的export方法的流程图:
这里存在三个包装类,分别是ProtocolListenerWrapper,QosProtocolWrapper,
ProtocolFilterWrapper,经过对InjvmProtocol进行层层包装之后,最终创建出来的exporter就是ProtocolFilterWrapper类型的了。