一、SPI机制:
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。
package com.example;
public interface Spi {
boolean isSupport(String name);
String sayHello();
}
ServiceLoader 会遍历所有 jar 查找 META-INF/services/com.example.Spi 文件
A 厂商提供实现
package com.a.example;
public class SpiAImpl implements Spi {
public boolean isSupport(String name) {
return "SPIA".equalsIgnoreCase(name.trim());
}
public String syaHello() {
return “hello 我是厂商 A”;
}
}
A 厂商提供的 jar 包中的 META-INF/services/com.example.Spi 文件内容为:
com.a.example.SpiAImpl #厂商 A 的 spi 实现全路径类名
B 厂商提供实现
package com.b.example;
public class SpiBImpl implements Spi {
public boolean isSupport(String name) {
return "SPIB".equalsIgnoreCase(name.trim());
}
public String syaHello() {
return “hello 我是厂商 B”;
}
}
B 厂商提供的 jar 包中的 META-INF/services/com.example.Spi 文件内容为:
com.b.example.SpiBImpl #厂商 B 的 spi 实现全路径类名
ServiceLoader.load(Spi.class) 读 取 厂 商 A 、 B 提 供 jar 包 中 的 文 件 ,ServiceLoader 实现了 Iterable 接口可通过 while for 循环语句遍历出所有实现。
一个接口多种实现,就如策略模式一样提供了策略的实现,但是没有提供策略的选择, 使用方可以根据 isSupport 方法根据业务传入厂商名来选择具体的厂商。
public class SpiFactory {
//读取配置获取所有实现
private static ServiceLoader spiLoader = ServiceLoader.load(Spi.class);
//根据名字选取对应实现
public static Spi getSpi(String name) {
for (Spi spi : spiLoader) {
if (spi.isSupport(name) ) {
return spi;
}
}
return null;
}}
二、dubbo实现的SPI机制:
1.标记哪些接口支持SPI 机制:Dubbo 提供一个注解 ,通过该注解标记的接口支持SPI功能。
2.SPI接口实现类的信息从哪里读取?
3.SPI接口实现类加载流程:
1、首选获取SPI接口的ExtensionLoader 类,每个接口会缓存ExtensionLoader 类,ExtensionLoader 类相当于ServiceLoader
2.通过ExtensionLoader 类加载接口的实现类
a) 先读取 SPI 注解的 value 值,有值作为默认扩展实现的 key
b) 依次读取路径的文件
META-INF/dubbo/internal/ com.alibaba.dubbo.rpc.Protocol
META-INF/dubbo/ com.alibaba.dubbo.rpc.Protocol
META-INF/services/ com.alibaba.dubbo.rpc.Protocol
逐行读取对应的实现类并通过反射构造实现类。
4.SPI接口实现类加载好了,n个实现类用那个?
dubbo 提供SPI 接口的决策类:适配器类。
为什么要创建设配类,一个接口多种实现, SPI 机制也是如此,这是策略模式,但是我们在代码执行过程中选择哪种具体的策略呢。 Dubbo 采用统一数据模式 com.alibaba.dubbo.common.URL( 它是 dubbo 定义的数据模型不是 jdk 的类),它会穿插于系统的整个执行过程, URL 中定义的协 议 类 型 字 段 protocol , 会 根 据 具 体 业 务 设 置 不 同 的 协 议 。url.getProtocol()值可以是 dubbo 也是可以 webservice, 可以是zookeeper 也可以是 redis。
dubbo有两种适配类的实现方式:
1、静态@Adaptive
2、动态生成适配器代码:
获取对应的策略适配器类:
1) 生成 Adaptive 代码 code
2) 利用 dubbo 的 spi 扩展机制获取 compiler 的设配类
3) 编译生成的 adaptive 代码
策略适配器类决策逻辑:更加URL 信息
SPI实现类依赖其他类怎么办?IOC
IOC 大 家 所 熟 知 的 ioc 是 spring 的 三 大 基 础 功 能 之 一 , dubbo 的ExtensionLoader 在加载扩展实现的时候内部实现了个简单的 ioc 机制来实现对扩展实现所依赖的参数的注入, dubbo 对扩展实现中公有的 set 方法且入参个数为一个的方法,尝试从对象工厂 ObjectFactory 获取值注入到扩展点实现中去。
1、Dubbo的容器工厂是怎样的?
dubbo 的容器工厂也是采用SPI 支持扩展。
它 跟 Compiler 接 口 一 样 设 配 类 注 解 @Adaptive 是 打 在 类AdaptiveExtensionFactory 上的不是通过 javassist 编译生成的。AdaptiveExtensionFactory 持有所有 ExtensionFactory 对象的集合, dubbo内 部 默 认 实 现 的 对 象 工 厂 是SpiExtensionFactory 和SpringExtensionFactory,他们经过 TreeMap 排好序的查找顺序是优先先从SpiExtensionFactory 获取,如果返回空在从 SpringExtensionFactory 获取。
工厂适配器类:
静态适配器类查找容器的SPI实现放入List<ExtensionFactory> factories
1) SpiExtensionFactory 工厂获取要被注入的对象,就是要获取 dubbo spi扩展的实现,所以传入的参数类型必须是接口类型并且接口上打上了@SPI 注解,返回的是一个设配类对象。
2) SpringExtensionFactory, Dubbo 利用 spring 的扩展机制跟 spring 做了很好的融合。在发布或者去引用一个服务的时候,会把 spring 的容器添加到 SpringExtensionFactory 工厂集合中去, 当 SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory 中的 spring 容器来获取要注入的对象