一、前言
在Duboo剖析-整体架构分析中介绍了dubbo中除了Service 和 Config 层为 API外,其他各层均为SPI,为SPI意味着下面各层都是组件化可以被替换的,这也是dubbo比较好的一点。
二、JDK中标准SPI
JDK 中的 SPI(Service Provider Interface)是面向接口编程的,服务规则提供者会在 JRE 的核心 API 里面提供服务访问接口,而具体实现则由其他开发商提供。
JDBC 4 是基于 SPI 机制来发现驱动提供商提供的实现类,提供者只需在 JDBC 驱动实现的Jar 的 META-INF/services/java.sql.Driver 文件里指定实现类的方式暴露驱动提供者。例如规范制定者在rt.jar包里面定义了 数据库 的驱动接口 java.sql.Driver。 MySQL 实现的 Jar,如下:
public class com.mysql.jdbc.Driver extends com.mysql.jdbc.NonRegisteringDriver implements java.sql.Driver
下面我们写个测试代码,看看具体是如何工作的。
然后引入 MySQL 驱动的 Jar 包,执行结果如下。
driver:class com.mysql.jdbc.Driver,loader:sun.misc.Launcher$AppClassLoader@4554617ccurrent thread contextloader:sun.misc.Launcher$AppClassLoader@4554617c
ServiceLoader loader:null
可知找到了mysql的驱动,如果你在引入Oracle的驱动的jar包后在运行,则会输出找到了mysql和Oracle的驱动,这也说明了,JDK标准的SPI会同时把spi接口的所有的实现类都提前加载好。
关于JDK中SPI的原理和具体使用可以参考 Java 类加载器揭秘 中
一种特殊的类加载器 ContextClassLoader 章节。
三、Dubbo增强的SPI
Dubbo 的扩展点加载是基于JDK 标准的 SPI 扩展点发现机制增强而来的,Dubbo 改进了 JDK 标准的 SPI 的以下问题:
-
JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
-
如果扩展点加载失败,就失败了,给用户没有任何通知。比如:JDK 标准的ScriptEngine,如果Ruby ScriptEngine 因为所依赖的 jruby.jar 不存在,导致 Ruby ScriptEngine 类加载失败,这个失败原因被吃掉了,当用户执行 ruby 脚本时,会报空指针异常,而不是报Ruby ScriptEngine不存在。
-
增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
下面看看Dubbo增强的SPI实现的时序图:
-
其中代码(1)获取当前SPI接口对应的ExtensionLoader
-
代码(2)获取适配器实例,内部首先获取该spi对应的所有实现类的Class对象,然后创建适配器实例,最后注入该适配器依赖的其他扩展点。
-
代码(8)根据名称获取具体的spi实现类,内部是创建一个实现类的实例,并使用warp类进行包装后返回。