Dubbo 是如何实现 SPI 机制?

2020-10-23 10:16:07 浏览数 (1)

SPI 全称是 Service Provider Interface,是一种服务发现机制。

如果对 Java SPI 实现不了解的同学,可以先看下往期的介绍:「周末福报」你了解 SPI 吗?


SPI 在 Dubbo 中是一个非常重要的模块。大量的 SPI 文件,如下图。

JDK SPI 的原理是在 resources/META-INF/service 目录下添加一个全路径接口名的文件,例如:org.apache.dubbo.common.extension.LoadingStrategy

,每一行都是接口实现类。

Dubbo SPI 是在 JDK SPI 的基础上重新实现了一套更强功能的 SPI 机制。它定义了两个新的目录 resources/META-INF/dubbo、resources/META-INF/dubbo.internal 与 JDK SPI 进行区分,例如:org.apache.dubbo.common.compiler.Compiler,每一行都是 Key=Value 的键值对格式。


SPI 源码

Dubbo SPI 的源码,存在放 dubbo-common 的 extension 扩展包中。

源码可以分为 @interface、interface、class,也就是注解、抽象接口、实现类。

@interface

Dubbo 的自定义注解 SPI、DisableInject、Adaptive、Activate。其中 SPI 是核心注解,它代表了接口启用 Dubbo SPI、也代表了接口使用哪个扩展实现类,例如:Protocol,将使用 DubboProtocol 这个实现类。

代码语言:javascript复制

DisableInject 是禁止注入注解。Adaptive 是自适应扩展注解,拥有一个字符串数组的 value 属性。Activate 是给定条件自动激活某些扩展的注解,拥有字符串数组的 group、value 属性。

interface

ExtensionFactory @SPI 修饰的工厂接口,提供一个返回值是泛型的 getExtension 接口。

LoadingStrategy 加载策略接口,使用 JDK SPI 机制提供三个实现类。

class

ServicesLoadingStrategy、DubboLoadingStrategy、DubboInternalLoadingStrategy 分别对应 META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal/ 目录。

ActivateComparator 实现了 Comparator 接口,实现了对 Activate 注解的 order 属性排序功能。

AdaptiveExtensionFactory 工厂类,初始化时会自动进行加载。

SpiExtensionFactory 工厂类,获取返回类型前需要校验 type 是接口,以及是否拥有 SPI 注解修饰。

代码语言:javascript复制
AdaptiveClassCodeGenerator 是 Adaptive Class 的代码生成器。

简单的解释就是,通过约定的规则进行的 Class 文件内容的字符串拼接。网上找到了一段拼接后的实例,可以参考一下:

代码语言:javascript复制
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    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);
    }
    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!");
    }
}
代码语言:javascript复制
ExtensionLoader 是加载 Dubbo 扩展的类。

大量的 ConcurrentMap、volatile 运用在扩展实例上,例如:cachedInstances 属性。


总结

Dubbo SPI 机制是通过自定义的注解、类生成器,以及加载扩展类的方式来实现的。

0 人点赞