Dubbo 是如何实现 SPI 机制?
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 这个实现类。
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 注解修饰。
AdaptiveClassCodeGenerator 是 Adaptive Class 的代码生成器。
简单的解释就是,通过约定的规则进行的 Class 文件内容的字符串拼接。网上找到了一段拼接后的实例,可以参考一下:
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!");
}
}
ExtensionLoader 是加载 Dubbo 扩展的类。
大量的 ConcurrentMap、volatile 运用在扩展实例上,例如:cachedInstances 属性。
总结
Dubbo SPI 机制是通过自定义的注解、类生成器,以及加载扩展类的方式来实现的。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 《前端那些事》从0到1开发简单脚手架
- CDH7.1.1启用Kerberos
- 持续代码质量管理-SonarQube Scanner部署 2.1. 软件安装2.2. 配置修改
- 《前端那些事》聊聊前端的按需加载
- 直播带货系统,滚动视图,上滑隐藏,下滑显示
- 持续代码质量管理-SonarQube-7.3简单使用 2.1. 查看配置2.2. 质量检测2.3. 浏览器查看
- 安装指定版本的docker服务
- 你学BFF和Serverless了吗
- 如何使用Java连接Kerberos的Phoenix
- docker swarm的常用操作
- 组件库源码中这些写法你掌握了吗?
- spark-2.4.0-hadoop2.7-安装部署 4.1. Spark安装4.2. 环境变量修改4.3. 配置修改4.4. 分发到其他机器4.5. 启动spark
- spark-2.4.0-hadoop2.7-高可用(HA)安装部署 5.1. Spark安装5.2. 环境变量修改5.3. 配置修改5.4. 分发到其他机器5.5.
- spark-2.4.0-hadoop2.7-简单操作 2.1. 相关截图
- Navicat Premium 12.0.24安装与激活(亲测已成功激活) 2.1. 下载激活文件2.2. 激活步骤准备工作2.3. 激活Navicat