dubbo源码一点都不难,前提是你会SPI机制

228 阅读3分钟

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类。ProtocolAdaptive类。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源码一点都不难,前提是你会SPI机制

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方法的流程图:

dubbo源码一点都不难,前提是你会SPI机制

这里存在三个包装类,分别是ProtocolListenerWrapper,QosProtocolWrapper,

ProtocolFilterWrapper,经过对InjvmProtocol进行层层包装之后,最终创建出来的exporter就是ProtocolFilterWrapper类型的了。

dubbo源码一点都不难,前提是你会SPI机制