Dubbo SPI
Java SPI
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。
原因
面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。这有点类似IOC的思想,将装配的控制权移到了程序之外。
API SPI
-
API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。
-
SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。
实现
- 定义接口
- 编写实现类
- 在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,在这个文件中写入接口的实现类的全限定名
比如接口全限定名:com.spi.cache.Cache
文件内容:
com.spi.cache.LocalCache
com.spi.cache.RedisCache - 最后调用ServiceLoader.load(UploadCDN.class)即可加载出以上两个实现类
原理
Class.forName反射加载并实例化进缓存
缺点
Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,无法按需加载
Dubbo SPI
![](https://img.haomeiwen.com/i8100269/b4ceb92533c719f8.png)
优点
Dubbo 的扩展点加载是基于JDK 标准的 SPI 扩展点发现机制增强而来的,Dubbo 改进了 JDK 标准的 SPI 的以下问题:
-
JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
-
如果扩展点加载失败,就失败了,给用户没有任何通知。比如:JDK 标准的ScriptEngine,如果Ruby ScriptEngine 因为所依赖的 jruby.jar 不存在,导致 Ruby ScriptEngine 类加载失败,这个失败原因被吃掉了,当用户执行 ruby 脚本时,会报空指针异常,而不是报Ruby ScriptEngine不存在。
-
增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
配置文件
文件名同样是接口全限定名,而内容是k-v形式,比如localCache=com.spi.cache.LocalCache,同样这样的方式实现按需加载
注解
@SPI注解(注解在类上)
标识了接口是一个扩展点,属性 value 用来指定默认适配扩展点的名称。
@Adaptive注解(注解在类型和方法上)
注解在类上 , 这个类就是缺省的适配扩展;注解在扩展点 @SPI 的方法上时 , dubbo 动态的生成一个这个扩展点的适配扩展类
@Activate注解 - 自适应扩展(注解在类型和方法上)
注解在扩展点的实现类上 ,表示了一个扩展类被获取到的的条件,根据 @Activate 中的 group 、 value 属性来过滤,
文件目录
- META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
- META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
- META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。
AOP
https://www.cnblogs.com/UYGHYTYH/p/13028591.html
![](https://img.haomeiwen.com/i8100269/b0b9d84d30d9bb9a.png)
利用符合构造函数要求的装饰者模式包装类来包装实现类
IOC
一个扩展点可以通过 setter 注入其它扩展点
原理
ExtensionLoader类似 Java SPI 中 ServiceLoader
- getExtension():主要用于获取名称为name的对应的子类的对象,这里如果子类对象如果有AOP相关的配置,这里也会对其进行封装;
- getAdaptiveExtension():使用定义的装饰类来封装目标子类,具体使用哪个子类可以在定义的装饰类中通过一定的条件进行配置;
- getExtensionLoader():加载当前接口的子类并且实例化一个ExtensionLoader对象。
使用:ExtensionLoader.getExtensionLoader(Cache.class).getExtension()或其他方法