
# 1. What Is SPI ?
简单来说,就是``Service Provider Interface``。比如你有个接口,现在这个接口有 3 个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要 SPI 了,需要根据**指定的配置**或者是**默认的配置**,去找到**对应的实现类**加载进来,然后用这个实现类的实例对象。
SPI 机制一般用在**插件扩展**的场景,比如说我们开发了一个给别人使用的开源框架,如果想让别人自己写个插件,接入到我们的开源框架里面,从而扩展某个功能,这个时候 SPI 思想就派上用场了。
# 2. Java 中 SPI 思想的体现
Java 中 SPI 思想的经典体现大家平时基本都在用,比如说 JDBC。
Java 定义了一套 JDBC 的接口,但是 Java 并没有提供 JDBC的实现类。
一般来说,我们要根据自己使用的数据库指定具体的实现类,比如 MySQL,我们就把 mysql-jdbc-connector.jar 引入进来;Oracle,我们就把 oracle-jdbc-connector.jar 引入进来。
系统运行时,遇到使用 JDBC的接口,就会在底层使用我们引入的那个 jar 中提供的实现类。
> Java SPI 有个弊端,就是一初始化就把所有实现类给加载进去。
# 3. Dubbo 的 SPI 思想
## 3.1 Dubbo 的微内核架构
Dubbp 的架构设计思想主要是两点:
- 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息;
- 采用 Microkernel + Plugin **(微内核+插件)**模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。
> 系统里抽象的各个模块,往往有很多不同的实现方案,对于好的设计来说:模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。
Dubbo 改进了 SPI 并重新命名为``ExtensionLoader``(扩展点机制),按照用户配置来指定加载模块,只需要约定一下路径即可。
微内核架构 (Microkernel architecture) 模式也被称为插件架构 (Plugin architecture) 模式。原本与内核集成在一起的组件会被分离出来,内核提供了特定的接口使得这些组件可以灵活的接入,这些组件在内核的管理下工作,但是这些组件可以独立的发展、更改(不会对现有系统造成改动),只要符合内核的接口即可。典型的例子比如 , Eclipse , IDEA 。
## 3.2 Dubbo 的微内核设计
一图胜千言:

> 图片来自:[理解 Dubbo SPI 扩展机制](https://my.oschina.net/j4love/blog/1813040)
dubbo 内核对扩展是无感的,完全不知道扩展的存在,内核代码中不会出现使用具体扩展的硬编码。
术语说明 :
- ``SPI`` : Service Provider Interface 。
- 扩展点 : 称 Dubbo 中被``@SPI``注解的``Interface``为一个扩展点。
- 扩展 : 被``@SPI``注解的``interface``的实现称为这个扩展点的一个扩展。
## 3.3 Dubbo SPI 约定
- 1.扩展点约定 : 扩展点必须是``interface``类型,同时必须被``@SPI``注解,满足这两点才是一个扩展点。
- 2.扩展定义约定 :在``META-INF/services/$扩展点接口的全类名``,``META-INF/dubbo/$扩展点接口的全类名``,``META-INF/dubbo/internal/$扩展点接口的全类名``,这些路径下定义的文件名称为``$扩展点接口的全类名``(不含.class或.java),文件中以键值对的方式配置扩展点的扩展实现。
例如文件``dubbo-x.x.x.jar!\META-INF\dubbo\internal\org.apache.dubbo.rpc.Protocol``中定义的扩展:
```
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
```
- 3.默认适应扩展 :对于被``@SPI("abc")``注解的``interface``,这个扩展点的缺省适应扩展就是 SPI 配置文件中 key 为 "abc" 的扩展。
如果存在被``@Adaptive``注解在类上的扩展点接口实现,那么这个类就作为扩展点的缺省适应扩展。
一个扩展点只能有一个缺省适应扩展,也就是说多个扩展中只能有一个在类上被``@Adaptive``注解,如果有多个 Dubbo 会抛出``IllegalStateException("More than 1 adaptive class found : ")``。
## 3.4 @SPI、@Adaptive、@Activate 的作用
- ``@SPI``(注解在类上) : ``@SPI``注解标识了接口是一个扩展点 ,属性 value 用来指定默认适配扩展点的名称(对应 SPI 配置文件中的 key)。
- ``@Activate``(注解在类型和方法上) :扩展点自动激活。``@Activate``注解在扩展点的实现类上,表示了一个扩展类被获取到的条件,符合条件就被获取,不符合条件就不获取。根据``@Activate``中的 group 、 value 属性来过滤。
> 具体可参考``ExtensionLoader``中的``getActivateExtension``函数。
- ``@Adaptive``(注解在类型和方法上):扩展点自适应。
``@Adaptive``注解在类上,这个类就是缺省的适配扩展。
``@Adaptive``注解在扩展点``interface``的方法上时,Dubbo 会动态的生成一个这个扩展点的适配扩展类。
> 生成代码,动态编译实例化 Class,名称为:``扩展点interface的简单类名 + $Adaptive``,例如 : ``ProxyFactory$Adpative``。
这么做的目的是为了**在运行时去适配不同的扩展实例**,在运行时通过传入的 URL 类型的参数或者内部含有获取 URL 方法的参数,从 URL 中获取到要使用的扩展类的名称,再去根据名称加载对应的扩展实例,用这个扩展实例对象调用相同的方法。
如果运行时没有适配到运行的扩展实例,那么就使用``@SPI``注解缺省指定的扩展。
通过这种方式就实现了运行时去适配到对应的扩展。
运行时动态生成的适配扩展类代码举例(基于 dubbo 2.7.1 版本通过CFR反编译得到) :
Protocol$Adaptive.class
```java
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Protocol;
import org.apache.dubbo.rpc.RpcException;
public class Protocol$Adaptive
implements Protocol {
@Override
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!");
}
@Override
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 Exporter export(Invoker invoker) throws RpcException {
if (invoker == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
}
if (invoker.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
}
URL uRL = invoker.getUrl();
// 从 URL 中获取扩展名称
String extName = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
if (extName == null) {
throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL.toString()).append(") use keys([protocol])").toString());
}
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
return protocol.export(invoker);
}
public Invoker refer(Class class_, URL uRL) throws RpcException {
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
// 从 URL 中获取扩展名称 , "dubbo" 是从 ExtensionLoader 对象中的 cachedDefaultName 属性获取到的
// cachedDefaultName 是扩展点上 @SPI 注解中 value 属性指定的
String extName = uRL2.getProtocol() == null ? "dubbo" : uRL2.getProtocol();
if (extName == null) {
throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL2.toString()).append(") use keys([protocol])").toString());
}
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
return protocol.refer(class_, uRL);
}
}
```
ProxyFactory$Adpative.class
```java
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.ProxyFactory;
import org.apache.dubbo.rpc.RpcException;
public class ProxyFactory$Adaptive
implements ProxyFactory {
public Object getProxy(Invoker invoker) throws RpcException {
if (invoker == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
}
if (invoker.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
}
URL uRL = invoker.getUrl();
// 从 URL 中获取扩展名称
String extName = uRL.getParameter("proxy", "javassist");
if (extName == null) {
throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (").append(uRL.toString()).append(") use keys([proxy])").toString());
}
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName);
return proxyFactory.getProxy(invoker);
}
public Object getProxy(Invoker invoker, boolean bl) throws RpcException {
if (invoker == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
}
if (invoker.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
}
URL uRL = invoker.getUrl();
// 从 URL 中获取扩展名称
String extName = uRL.getParameter("proxy", "javassist");
if (extName == null) {
throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (").append(uRL.toString()).append(") use keys([proxy])").toString());
}
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName);
return proxyFactory.getProxy(invoker, bl);
}
public Invoker getInvoker(Object object, Class class_, URL uRL) throws RpcException {
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
// 从 URL 中获取扩展名称
String extName = uRL2.getParameter("proxy", "javassist");
if (extName == null) {
throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (").append(uRL2.toString()).append(") use keys([proxy])").toString());
}
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName);
return proxyFactory.getInvoker(object, class_, uRL);
}
}
```
其实这些代码都是模板代码,最核心的代码就只有一行,这行代码是去获取一个指定名称的扩展实例对象 :
```java
ExtensionLoader.getExtensionLoader(Xxx.class).getExtension(extName);
```
## 3.5 扩展加载器``ExtensionLoader``
扩展加载器绝对是一个核心组件了 ,它控制着 dubbo 内部所有扩展点的初始化、加载扩展的过程。这个类的源码是很有必要深入学习的。

> 图片来自:[理解 Dubbo SPI 扩展机制](https://my.oschina.net/j4love/blog/1813040)
``ExtensionLoader``中会存储两个静态属性:
- ``EXTENSION_LOADERS``:保存了内核开放的扩展点对应的 ``ExtensionLoader``实例对象(说明了**一种扩展点有一个对应的``ExtensionLoader``对象**)。
- ``EXTENSION_INSTANCES``:保存了扩展类型(Class)和扩展类型的实例对象。
``ExtensionLoader``对象中的其他主要属性 :
```java
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 是抛出的异常。
## 4. Dubbo SPI 和 JDK SPI 区别
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
---
附: dubbo 开放的扩展点
```
org.apache.dubbo.cache.CacheFactory
org.apache.dubbo.common.compiler.Compiler
org.apache.dubbo.common.extension.ExtensionFactory
org.apache.dubbo.common.logger.LoggerAdapter
org.apache.dubbo.common.serialize.Serialization
org.apache.dubbo.common.status.StatusChecker
org.apache.dubbo.common.store.DataStore
org.apache.dubbo.common.threadpool.ThreadPool
org.apache.dubbo.container.Container
org.apache.dubbo.container.page.PageHandler
org.apache.dubbo.monitor.MonitorFactory
org.apache.dubbo.registry.RegistryFactory
org.apache.dubbo.remoting.Codec2
org.apache.dubbo.remoting.Dispatcher
org.apache.dubbo.remoting.exchange.Exchanger
org.apache.dubbo.remoting.http.HttpBinder
org.apache.dubbo.remoting.p2p.Networker
org.apache.dubbo.remoting.telnet.TelnetHandler
org.apache.dubbo.remoting.Transporter
org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter
org.apache.dubbo.rpc.cluster.Cluster
org.apache.dubbo.rpc.cluster.ConfiguratorFactory
org.apache.dubbo.rpc.cluster.LoadBalance
org.apache.dubbo.rpc.cluster.Merger
org.apache.dubbo.rpc.cluster.RouterFactory
org.apache.dubbo.rpc.Filter
org.apache.dubbo.rpc.InvokerListener
org.apache.dubbo.rpc.Protocol
org.apache.dubbo.rpc.protocol.thrift.ClassNameGenerator
org.apache.dubbo.rpc.ProxyFactory
org.apache.dubbo.validation.Validation
```
---
参考:
[理解 Dubbo SPI 扩展机制](https://my.oschina.net/j4love/blog/1813040)
[DUBBO原理、应用与面经总结](https://www.jianshu.com/p/292fcdcfe41e)
Dubbo 学习2——SPI 扩展机制