## JDK 的 SPI 思想
SPI,即 Service Provider Interface。在面向对象的设计里面,模块之间推荐基于接口编程,而不是对实现类进行硬编码,这样做也是为了模块设计的可插拔原则。
比较典型的应用,如 JDBC,Java 定义了一套 JDBC 的接口,但是 Java 本身并不提供对 JDBC 的实现类,而是开发者根据项目实际使用的数据库来选择驱动程序 jar 包,比如 mysql,你就将 mysql-jdbc-connector.jar 引入进来;oracle,你就将 oracle-jdbc-connector.jar 引入进来。在系统跑的时候,碰到你使用 jdbc 的接口,他会在底层使用你引入的那个 jar 中提供的实现类。
## Dubbo 的 SPI 扩展机制原理
dubbo 自己实现了一套 SPI 机制,并对 JDK 的 SPI 进行了改进。
1. JDK 标准的 SPI 只能通过遍历来查找扩展点和实例化,有可能导致一次性加载所有的扩展点,如果不是所有的扩展点都被用到,就会导致资源的浪费。dubbo 每个扩展点都有多种实现,例如:com.alibaba.dubbo.rpc.Protocol 接口有 InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol 等实现,如果只是用到其中一个实现,可是加载了全部的实现,会导致资源的浪费。
2. 对配置文件中扩展实现的格式的修改,例如,META-INF/dubbo/com.xxx.Protocol 里的 com.foo.XxxProtocol 格式 改为了 xxx = com.foo.XxxProtocol 这种以键值对的形式,这样做的目的是为了让我们更容易的定位到问题。比如,由于第三方库不存在,无法初始化,导致无法加载扩展点(“A”),当用户配置使用 A 时,dubbo 就会报无法加载扩展点的错误,而不是报哪些扩展点的实现加载失败以及错误原因,**这是因为原来的配置格式没有记录扩展名的 id,导致 dubbo 无法抛出较为精准的异常,这会加大排查问题的难度**。所以改成 key-value 的形式来进行配置。
3. dubbo 的 SPI 机制增加了对 IOC、AOP 的支持,一个扩展点可以直接通过 setter 注入到其他扩展点。
下面我们看一下 Dubbo 的 SPI 扩展机制实现的结构目录。
![avatar](../../../images/Dubbo/SPI组件目录结构.png)
### SPI 注解
首先看一下 SPI 注解。在某个接口上加上 @SPI 注解后,表明该接口为可扩展接口。比如,协议扩展接口 Protocol,如果使用者在 <dubbo:protocol />、<dubbo:service />、<dubbo:reference /> 都没有指定 protocol 属性 的话,那么就默认使用 DubboProtocol 作为接口 Protocol 的实现,因为在 Protocol 上有 @SPI("dubbo")注解。而这个 protocol 属性值 或者默认值会被当作该接口的实现类中的一个 key,dubbo 会去 META-INF.dubbo.internal 下的 com.alibaba.dubbo.rpc.Protocol 文件中找该 key 对应的 value,源码如下。
```java
/**
* 协议接口
* Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
*/
@SPI("dubbo")
public interface Protocol {
/**
* Get default port when user doesn't config the port.
*/
int getDefaultPort();
/**
* 暴露远程服务:
* 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();
* 2. export() 必须是幂等的,也就是暴露同一个 URL 的 Invoker 两次,和暴露一次没有区别。
* 3. export() 传入的 Invoker 由框架实现并传入,协议不需要关心。
*
* @param 服务的类型
* @param invoker 服务的执行体
* @return exporter 暴露服务的引用,用于取消暴露
* @throws RpcException 当暴露服务出错时抛出,比如端口已占用
*/
@Adaptive
Exporter export(Invoker invoker) throws RpcException;
/**
* 引用远程服务:
* 1. 当用户调用 refer() 所返回的 Invoker 对象的 invoke() 方法时,协议需相应执行同 URL 远端 export() 传入的 Invoker 对象的 invoke() 方法。
* 2. refer() 返回的 Invoker 由协议实现,协议通常需要在此 Invoker 中发送远程请求。
* 3. 当 url 中有设置 check=false 时,连接失败不能抛出异常,并内部自动恢复。
*
* @param 服务的类型
* @param type 服务的类型
* @param url 远程服务的URL地址
* @return invoker 服务的本地代理
* @throws RpcException 当连接服务提供方失败时抛出
*/
@Adaptive
Invoker refer(Class type, URL url) throws RpcException;
/**
* 释放协议:
* 1. 取消该协议所有已经暴露和引用的服务。
* 2. 释放协议所占用的所有资源,比如连接和端口。
* 3. 协议在释放后,依然能暴露和引用新的服务。
*/
void destroy();
}
/**
* 扩展点接口的标识。
* 扩展点声明配置文件,格式修改。
* 以Protocol示例,配置文件META-INF/dubbo/com.xxx.Protocol内容:
* 由
* com.foo.XxxProtocol
* com.foo.YyyProtocol
* 改成使用KV格式
* xxx=com.foo.XxxProtocol
* yyy=com.foo.YyyProtocol
*
* 原因:
* 当扩展点的static字段或方法签名上引用了三方库,
* 如果三方库不存在,会导致类初始化失败,
* Extension标识Dubbo就拿不到了,异常信息就和配置信息对应不起来。
*
* 比如:
* Extension("mina")加载失败,
* 当用户配置使用mina时,就会报找不到扩展点mina,
* 而不是报加载扩展点失败,等难以定位具体问题的错误。
*
* @author william.liangf
* @author ding.lid
* @export
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
/**
* default extension name
*
* 默认拓展名
*/
String value() default "";
}
// 配置文件 com.alibaba.dubbo.rpc.Protocol 中的内容
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
```
value 就是该 Protocol 接口 的实现类 DubboProtocol,这样就做到了 SPI 扩展。
### ExtensionLoader
ExtensionLoader 扩展加载器,这是 dubbo 实现 SPI 扩展机制 的核心,几乎所有实现的逻辑都被封装在 ExtensionLoader 中,其源码如下。
```java
/**
* 拓展加载器,Dubbo使用的扩展点获取
*
* - 自动注入关联扩展点。
* - 自动Wrap上扩展点的Wrap类。
* - 缺省获得的的扩展点是一个Adaptive Instance。
*
*
* 另外,该类同时是 ExtensionLoader 的管理容器,例如 {@link #EXTENSION_INSTANCES} 、{@link #EXTENSION_INSTANCES} 属性。
*/
@SuppressWarnings("deprecation")
public class ExtensionLoader {
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
// ============================== 静态属性 ==============================
/**
* 拓展加载器集合
*
* key:拓展接口
*/
private static final ConcurrentMap, ExtensionLoader>> EXTENSION_LOADERS = new ConcurrentHashMap, ExtensionLoader>>();
/**
* 拓展实现类集合
*
* key:拓展实现类
* value:拓展对象。
*
* 例如,key 为 Class
* value 为 AccessLogFilter 对象
*/
private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap, Object>();
// ============================== 对象属性 ==============================
/**
* 拓展接口。
* 例如,Protocol
*/
private final Class> type;
/**
* 对象工厂
*
* 用于调用 {@link #injectExtension(Object)} 方法,向拓展对象注入依赖的属性。
*
* 例如,StubProxyFactoryWrapper 中有 `Protocol protocol` 属性。
*/
private final ExtensionFactory objectFactory;
/**
* 缓存的拓展名与拓展类的映射。
*
* 和 {@link #cachedClasses} 的 KV 对调。
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap, String>();
/**
* 缓存的拓展实现类集合。
*
* 不包含如下两种类型:
* 1. 自适应拓展实现类。例如 AdaptiveExtensionFactory
* 2. 带唯一参数为拓展接口的构造方法的实现类,或者说拓展 Wrapper 实现类。例如,ProtocolFilterWrapper 。
* 拓展 Wrapper 实现类,会添加到 {@link #cachedWrapperClasses} 中
*
* 通过 {@link #loadExtensionClasses} 加载
*/
private final Holder