一、前言
在之前的dubbo源码分析我们以及了解了dubbo相关的架构、用法和原理,但是提到dubbo我们就不得不提其中spi机制,dubbo源码中使用了大量的spi机制,其所有的核心组件都做成了基于spi的实现方式,比如Protocol层=>DubboProtocol,Cluster层=>FailoverCluster等,spi这种机制可以实现这个组件的插拔式使用(一个接口多种实现,可以随时调整实现方式)同时支持我们进行定制化扩展。
下图为dubbo使用spi形式引入的内部组件
下面就让我们一起来探究一下dubbo的spi相关机制。
二、java SPI
在介dubbo的spi之前我们需要先了解一下什么是spi以及jdk默认支持的spi机制。
2.1、什么是SPI
SPI的全名为Service Provider Interface,模块之间相互调用基于接口编程,需要为某个接口寻找服务实现将装配的控制权移到程序之外的机制。
要使用Java SPI,需要遵循如下约定:
- 1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
- 2、接口实现类所在的jar包放在主程序的classpath中;
- 3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
- 4、SPI的实现类必须携带一个不带参数的构造方法;
像我们熟悉的java的jdbc,jdk定义了一套接口规范,不同的数据库厂商提供不同的实现。
mysql数据厂商实现:
2.2、java SPI 示范例
项目结构
spi-api: 提供接口规范
public interface SpiService {
void say();
}
**spi-impl1、spi-impl2: 提供不同实现 **
public class SpiServiceImplOne implements SpiService {
public void say() {
System.out.println("i am SpiServiceImplOne");
}
}
其他实现类类似
spi配置 不同实现都有相似配置
@Test
public void testSpi(){
ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class);
for (SpiService o : serviceLoader) {
o.say();
}
}
测试结果
介绍java提供的spi机制、实例、源码分析,优缺点 引申出dubbo的spi机制
2.3、java spi源码解析
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取classLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//获取接口类
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//获取类记载器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
//安全管理
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
//重新家在
reload();
}
public void reload() {
//清空缓存中的接口对应的实例对象
providers.clear();
//创建LazyIterator 该对象是用来循环遍历实例化接口实现类的
lookupIterator = new LazyIterator(service, loader);
}
从测试类ServiceLoader的load()开始溯源,最终其使用两个参数对象 服务接口的class和类加载器创建LazyIterator对象,该对象是实际进行服务实例化的,其实现了Iterator迭代器hasNext()=>hasNextService()和next()=>nextService().
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//获取spi配置文件路径 classPath /META-INF/service/${接口全限定类名}
String fullName = PREFIX + service.getName();
//根据配置文件路径记载配置文件到configs
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
//读取并解析配置文件获取其中的配置信息(配置信息可以是多个实现类是一个列表)则pending为接口实现名的列表迭代对象
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
//从迭代器获取一个接口实现 使用nextName接收
nextName = pending.next()