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 机制是通过自定义的注解、类生成器,以及加载扩展类的方式来实现的。