理解 Dubbo SPI 扩展机制
写在前面
最近接触了 gRPC 体会到虽然众多 RPC 框架各有各的特点但是他们提供的特性和功能有很多的相似之处 , 这就说明他们面对同样的分布式系统带来的问题。从 2016 年左右开始接触到 dubbo ,基本停留在使用的层面,对 dubbo 的设计以及着重要解决的问题都没有系统的研究过,通过对 dubbo 和其他类似 RPC 产品的系统学习 ,学习分布式系统中面临的共同问题以及解决之道。
微内核架构
微内核架构 (Microkernel architecture) 模式也被称为插件架构 (Plugin architecture) 模式。原本与内核集成在一起的组件会被分离出来,内核提供了特定的接口使得这些组件可以灵活的接入,这些组件在内核的管理下工作,但是这些组件可以独立的发展、更改(不会对现有系统造成改动),只要符合内核的接口即可。典型的例子比如 , Eclipse , IDEA 。

Dubbo 的微内核设计
根据我个人对 Dubbo 微内核设计的理解,以及阅读源码后总结。视觉总是最直观的,可以让大脑最快速度的有一个最直观的认识,一开始就一头深入到源码的细节中只会让人迷糊。不理解 Dubbo 的微内核设计架构的话,学习起来会走不少弯路。

dubbo 内核对扩展是无感的 , 完全不知道扩展的存在 , 内核代码中不会出现使用具体扩展的硬编码。
术语说明 :
SPI : Service Provider Interface 。
扩展点 : 称 Dubbo 中被 @SPI 注解的 Interface 为一个扩展点。
扩展 : 被 @SPI 注解的 Interface 的实现称为这个扩展点的一个扩展。
Dubbo SPI 约定
扩展点约定 : 扩展点必须是 Interface 类型 , 必须被 @SPI 注解 , 满足这两点才是一个扩展点。
扩展定义约定 : 在 META-INF/services/$扩展点接口的全类名 , META-INF/dubbo/$扩展点接口的全类名 , META-INF/dubbo/internal/$扩展点接口的全类名 , 这些路径下定义的文件名称为 $扩展点接口的全类名 , 文件中以键值对的方式配置扩展点的扩展实现。例如文件 META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory 中定义的扩展 :
adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
默认适应扩展 : 被 @SPI("abc") 注解的 Interface , 那么这个扩展点的缺省适应扩展就是 SPI 配置文件中 key 为 "abc" 的扩展。如果存在被 @Adaptive 注解在类上的扩展点接口实现 ,那么这个类就作为扩展点的缺省适应扩展, 一个扩展点只能有一个缺省适应扩展也就是说多个扩展中只能有一个在类上被 @Adaptive 注解,如果有多个 dubbo 会抛出 IllegalStateException("More than 1 adaptive class found : ")。
@SPI 、@Adaptive 、@Activate 作用
@SPI (注解在类上) : @SPI 注解标识了接口是一个扩展点 , 属性 value 用来指定默认适配扩展点的名称。
@Activate (注解在类型和方法上) : @Activate 注解在扩展点的实现类上 ,表示了一个扩展类被获取到的的条件,符合条件就被获取,不符合条件就不获取 ,根据 @Activate 中的 group 、 value 属性来过滤 。具体参考 ExtensionLoader 中的 getActivateExtension 函数。
@Adaptive (注解在类型和方法上) : @Adaptive 注解在类上 , 这个类就是缺省的适配扩展。@Adaptive 注解在扩展点 Interface 的方法上时 , dubbo 动态的生成一个这个扩展点的适配扩展类(生成代码 ,动态编译实例化 Class ),名称为 扩展点 Interface 的简单类名 + $Adaptive ,例如 : ProxyFactory$Adpative 。这么做的目的是为了在运行时去适配不同的扩展实例 , 在运行时通过传入的 URL 类型的参数或者内部含有获取 URL 方法的参数 ,从 URL 中获取到要使用的扩展类的名称 ,再去根据名称加载对应的扩展实例 ,用这个扩展实例对象调用相同的方法 。如果运行时没有适配到运行的扩展实例 , 那么就使用 @SPI 注解缺省指定的扩展。通过这种方式就实现了运行时去适配到对应的扩展。
运行时动态生成的适配扩展类代码 :
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
// 从 URL 中获取扩展名称 , "dubbo" 是从 ExtensionLoader 对象中的 cachedDefaultName
// 属性获取到的 , cachedDefaultName 是扩展点上 @SPI 注解中 value 属性指定的
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
// 从 URL 中获取扩展名称
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory {
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
// 从 URL 中获取扩展名称
String extName = url.getParameter("proxy", "javassist");
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws java.lang.Object {
if (arg2 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg2;
// 从 URL 中获取扩展名称
String extName = url.getParameter("proxy", "javassist");
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
}
这些代码都是模板代码 , 最核心的代码就只有一行 , 这行代码是去获取一个指定名称的扩展实例对象 :
ExtensionLoader.getExtensionLoader(Xxx.class).getExtension(extName);
在使用 dubbo 生成的源码时要注意 , 它生成的代码是有错误的, 比如 Protocol$Adpative 类的中的 refer 和 export 方法签名 throws 的不是 Protocol 接口中方法定义抛出的 RpcException , 而是 Class ,和 Invoker 。 这个问题存在于 dubbo 2.5.4 之前的版本中,在 2.5.4 版本中修复了。
扩展加载器 ExtensionLoader
扩展加载器绝对是一个核心组件了 ,它控制着 dubbo 内部所有扩展点的初始化、加载扩展的过程。这个类的源码是很有必要深入学习的。从 Dubbo 内核设计简图可以看到,现在的学习还没有接触到 dubbo 的内核。

ExtensionLoader 中会存储两个静态属性 , EXTENSION_LOADERS 保存了内核开放的扩展点对应的 ExtensionLoader 实例对象 (说明了一种扩展点有一个对应的 ExtensionLoader 对象)。EXTENSION_INSTANCES 保存了扩展类型 (Class) 和扩展类型的实例对象。
ExtensionLoader 对象中的属性 :
Class<?> type;
ExtensionFactory objectFactory;
ConcurrentMap<Class<?>, String> cachedNames;
Holder<Map<String, Class<?>>> cachedClasses;
Map<String, Activate> cachedActivates;
Class<?> cachedAdaptiveClass;
ConcurrentMap<String, Holder<Object>> cachedInstances;
String cachedDefaultName;
Holder<Object> cachedAdaptiveInstance;
Throwable createAdaptiveInstanceError;
Set<Class<?>> cachedWrapperClasses;
Map<String, IllegalStateException> exceptions;
type : 被 @SPI 注解的 Interface , 也就是扩展点。
objectFactory : 扩展工厂,可以从中获取到扩展类型实例对象 ,缺省为 AdaptiveExtensionFactory。
cachedNames : 保存不满足装饰模式(不存在只有一个参数,并且参数是扩展点类型实例对象的构造函数)的扩展的名称。
cachedClasses : 保存不满足装饰模式的扩展的 Class 实例 , 扩展的名称作为 key , Class 实例作为 value。
cachedActivates : 保存不满足装饰模式 , 被 @Activate 注解的扩展的 Class 实例。
cachedAdaptiveClass : 被 @Adpative 注解的扩展的 Class 实例 。
cachedInstances : 保存扩展的名称和实例对象 , 扩展名称为 key , 扩展实例为 value。
cachedDefaultName : 扩展点上 @SPI 注解指定的缺省适配扩展。
createAdaptiveInstanceError : 创建适配扩展实例过程中抛出的异常。
cachedWrapperClasses : 满足装饰模式的扩展的 Class 实例。
exceptions : 保存在加载扩展点配置文件时,加载扩展点过程中抛出的异常 , key 是当前读取的扩展点配置文件的一行 , value 是抛出的异常。
附: dubbo 开放的扩展点
com.alibaba.dubbo.cache.CacheFactory
com.alibaba.dubbo.common.compiler.Compiler
com.alibaba.dubbo.common.extension.ExtensionFactory
com.alibaba.dubbo.common.logger.LoggerAdapter
com.alibaba.dubbo.common.serialize.Serialization
com.alibaba.dubbo.common.status.StatusChecker
com.alibaba.dubbo.common.store.DataStore
com.alibaba.dubbo.common.threadpool.ThreadPool
com.alibaba.dubbo.container.Container
com.alibaba.dubbo.container.page.PageHandler
com.alibaba.dubbo.monitor.MonitorFactory
com.alibaba.dubbo.registry.RegistryFactory
com.alibaba.dubbo.remoting.Codec2
com.alibaba.dubbo.remoting.Dispatcher
com.alibaba.dubbo.remoting.exchange.Exchanger
com.alibaba.dubbo.remoting.http.HttpBinder
com.alibaba.dubbo.remoting.p2p.Networker
com.alibaba.dubbo.remoting.telnet.TelnetHandler
com.alibaba.dubbo.remoting.Transporter
com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter
com.alibaba.dubbo.rpc.cluster.Cluster
com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory
com.alibaba.dubbo.rpc.cluster.LoadBalance
com.alibaba.dubbo.rpc.cluster.Merger
com.alibaba.dubbo.rpc.cluster.RouterFactory
com.alibaba.dubbo.rpc.Filter
com.alibaba.dubbo.rpc.InvokerListener
com.alibaba.dubbo.rpc.Protocol
com.alibaba.dubbo.rpc.protocol.thrift.ClassNameGenerator
com.alibaba.dubbo.rpc.ProxyFactory
com.alibaba.dubbo.validation.Validation
原文链接:https://my.oschina.net/j4love/blog/1813040
理解 Dubbo SPI 扩展机制的更多相关文章
- [转] 理解 Dubbo SPI 扩展机制
写在前面 最近接触了 gRPC 体会到虽然众多 RPC 框架各有各的特点但是他们提供的特性和功能有很多的相似之处 , 这就说明他们面对同样的分布式系统带来的问题.从 2016 年左右开始接触到 dub ...
- Dubbo中SPI扩展机制解析
dubbo的SPI机制类似与Java的SPI,Java的SPI会一次性的实例化所有扩展点的实现,有点显得浪费资源. dubbo的扩展机制可以方便的获取某一个想要的扩展实现,每个实现都有自己的name, ...
- 聊聊Dubbo - Dubbo可扩展机制实战
1. Dubbo的扩展机制 在Dubbo的官网上,Dubbo描述自己是一个高性能的RPC框架.今天我想聊聊Dubbo的另一个很棒的特性, 就是它的可扩展性. 如同罗马不是一天建成的,任何系统都一定是从 ...
- 使用Dubbo的SPI扩展机制实现自定义LoadBalance——方法二 不改源码添加META-INF/dubbo元数据
一.官网提供的方法 参考官网 http://dubbo.apache.org/zh-cn/docs/dev/impls/load-balance.html 二.方法总结 在工程中创建类并实现LoadB ...
- 使用Dubbo的SPI扩展机制实现自定义LoadBalance——方法一 修改Dubbo源代码
一. 拉取源码 到Dubbo官网 https://github.com/apache/incubator-dubbo/tree/2.5.x 下载源码,解压. 二. 导入IDEA 选择解压后的源码目录, ...
- Java中的SPI扩展机制(有demo)
参考连接:https://www.jianshu.com/p/3a3edbcd8f24 一.什么是SPI SPI ,全称为 Service Provider Interface,是一种服务发现机制.它 ...
- Dubbo 扩展点加载机制:从 Java SPI 到 Dubbo SPI
SPI 全称为 Service Provider Interface,是一种服务发现机制.当程序运行调用接口时,会根据配置文件或默认规则信息加载对应的实现类.所以在程序中并没有直接指定使用接口的哪个实 ...
- 从ExtensionLoader理解Dubbo扩展机制
Dubbo的扩展机制是怎么实现的?最简单的回答就是@SPI. Dubbo的插件化思路来源于Java SPI. JAVA SPI 机制 SPI的全名为Service Provider Int ...
- Dubbo系列之 (一)SPI扩展
一.基础铺垫 1.@SPI .@Activate. @Adaptive a.对于 @SPI,Dubbo默认的特性扩展接口,都必须打上这个@SPI,标识这是个Dubbo扩展点.如果自己需要新增dubbo ...
随机推荐
- 【BZOJ1935/4822】[Shoi2007]Tree 园丁的烦恼/[Cqoi2017]老C的任务 树状数组
题意:两道题差不多,都是给你一堆平面上的点,每个点有权值,然后m次询问求某一矩形区域内的点权和 题解:先离散化,然后将询问拆成左右两条线段,然后将点和这些线段一起按x坐标排序,在y轴上维护树状数组.然 ...
- ios笔试题(选择题)
1-10 C语言 & 计算机基础 1.请看下面一段代码 static int a = 1; int main(){ int b = 2; char *c = NULL; c = (char * ...
- 《从零开始学Swift》学习笔记(Day 65)——Cocoa Touch设计模式及应用之选择器
原创文章,欢迎转载.转载请注明:关东升的博客 实现目标与动作关联使用UIControl类addTarget(_:action:forControlEvents:)方法,示例代码如下: button.a ...
- Arduino开发版学习计划--蓝牙控制小车行走
蓝牙模块一共6个引脚,我们一般只需要接4个线就可以了,分别是VCC.GND.TXD.RXD这四个引脚,我们分别接到arduino板子上,VCC接3.3V,GND接板子的GND,蓝牙TXD接板子的RXD ...
- 安装TortoiseSVN客户端时遇到的异常
环境:WindowsXP,安装 双击SVN安装程序"TortoiseSVN-1.8.5.25224-win32-svn-1.8.8.msi"后,出现 "无法通过Sindo ...
- java读代码步骤
一.读代码的步骤 1.知道代码时用什么IDE开发的 2.将代码导入到IDE 3.连接数据库 A)连接到测试数据库 B)有sql脚本,在本地创建一个数据库,执行脚本,建立数据结构和导入数据. 4.尝试运 ...
- Macbook pro 13" compile Apollo 2.5
STEPS: 0. Install Homebrew 1. Install 'Docker for Mac 18.03+',配置CPUs (n个CPUs,Bazel开n个线程编译), Memory ...
- vue下使用echarts折线图及其横坐标拖拽功能
vue页面中使用折线图,并且有时间段筛选.因此就需要用到横坐标的拖拽功能. 界面效果如下: 现在来看这个效果的实现代码: drawLine() { let that = this, lineDate ...
- HTTP API 设计指南(转)
add by zhj (2014-12-16): 今天才知道,原画HeroKu是国外一个很有名的PaaS提供商,公司很可能会将app迁移到他们那里 英文原文: HTTP API Design Guid ...
- git学习------>从SVN迁移到Git之后,项目开发代码继续在SVN提交,如何同步迁移之后继续在SVN提交的代码到Git?
最近逐步逐步的将公司的项目都从SVN往Git迁移了,但是想团队成员都能够一步到位就迁移到Git是不可能的,因为还有大部分人都还不会Git,所以整个过渡过程估计得大半年. 因此导致虽然项目迁移过来了,但 ...