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 扩展机制
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
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
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);
}
}
其实这些代码都是模板代码,最核心的代码就只有一行,这行代码是去获取一个指定名称的扩展实例对象 :
ExtensionLoader.getExtensionLoader(Xxx.class).getExtension(extName);
3.5 扩展加载器ExtensionLoader
扩展加载器绝对是一个核心组件了 ,它控制着 dubbo 内部所有扩展点的初始化、加载扩展的过程。这个类的源码是很有必要深入学习的。
图片来自:理解 Dubbo SPI 扩展机制
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 是抛出的异常。
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
评论区