最近在阅读《架构修炼之道》,觉得里面的内容干货满满,都是经过实践总结出来的经验,包括各个方面的相关技术和方案,于是决定对里面的内容做一个系列分享给大家,今天是我们的系列之一:SPI机制。
一、API和SPI区别
1.1 API
为了做一个简单区别,先来说一下API。对于API,在日常开发工作中我们已经耳熟能详了,但要给它下一个定义,似乎又不是简单的事情。我们直接去认识它的英文全称Application Programming Interface更容易理解:应用编程接口,根据定义拆解,是一个接口,基于应用,且是可编程的。目前一般企业架构内部都会有一个RPC框架,如果没有自研的也都在使用开源的比如比较有名的Dubbo。业务需求确定之后,技术侧也会相互沟通需要对方提供几个API接口,从面向接口编程的角度而言,这里的接口即API(图1)。

图1
另外还有当前的开放平台也是把企业内部的接口通过API网关暴露出去提供给第三方开发者如图2所示。这两种情况下说的都是API。接口自己定义,自己来实现,然后暴露出去供别人调用,这是属于API概念(图2)。

图2
1.2 SPI
SPI(Service Provider Interface) 一方制定接口,另外一方提供接口实现,比如在开放平台中,平台制定接口,第三方去实现。这里的第三方是指开放平台的开发者。接口自己定义,但是并不实现,而是由其他模块或系统去实现,这就是SPI。
1.3 API和SPI的区别
API是属于任意的调入,调出方式,这里的任意特指所有接口参数数量,类型都不固定,比如企业内部发布了很多业务的接口,同时很多业务的接口都发布到了API Gateway上从而开放出去。SPI是属于标准化的调入,调出方式,需要有一方来创建接口标准,参数数量和类型固定之后,由第三方来实现。下图3展示了API和SPI的区别。

图3 API和SPI的区别
二、SPI的机制和原理
2.1 SPI的机制
Service provider提供Interface的具体实现后,在目录META-INF/services下的文件(以Interface全路径命名)中添加具体实现类的全路径名;
接口实现类的jar包存放在所使用的类加载器能找到的地方。
应用程序使用ServiceLoader动态加载实现类(根据目录META-INF/services下的配置文件找到实现类的全限定名并调用classloader来加载实现类到JVM);
SPI的实现类必须具有无参数的构造方法。
SPI加载的核心就是ClassLoader的getResource系列方法,jdk提供了一个工具类,就是上面说的ServiceLoader。
2.2 SPI原理
(1)应用程序调用 ServiceLoader.load 方法,ServiceLoader.load 方法内先创建一个新的ServiceLoader,并实例化该类中的成员变数,包括:
ClassLoader loader(类载入器)
AccessControlContext acc(访问控制器)
LinkedHashMap<String, S> providers(用于缓存载入成功的类)
LazyIterator lookupIterator(实现迭代器功能)
(2) 应用程序通过迭代器获取对象实例
ServiceLoader 先判断成员变量 providers 对象中否有缓存实例对象,如果有缓存,直接返回。
如果没有缓存,执行类的装载:读取 META-INF/services/ 下的配置文件,获得所有能被实例化的类的名称,通过反射方法 Class.forName() 载入类对象,并用 instance() 方法将类实例化。把实例化后的类缓存到providers 对象中然后返回实例对象。
下面是SPI原理关键实现ServiceLoader的部分核心源码:
public final class ServiceLoader<S> implements Iterable<S> {//扫描目录前缀private static final String PREFIX = "META-INF/services/";// 被加载的类或接口private final Class<S> service;// 用于定位、加载和实例化实现方实现的类的类加载器private final ClassLoader loader;// 上下文对象private final AccessControlContext acc;// 按照实例化的顺序缓存已经实例化的类private LinkedHashMap<String, S> providers = new LinkedHashMap<>();// 懒查找迭代器private java.util.ServiceLoader.LazyIterator lookupIterator;// 私有内部类,提供对所有的service的类的加载与实例化private class LazyIterator implements Iterator<S> {Class<S> service;ClassLoader loader;Enumeration<URL> configs = null;String nextName = null;//...private boolean hasNextService() {if (configs == null) {try {//获取目录下所有的类 转换为-URL,工作流的方式获取每一行的数据并截取第一个‘#’之前的数据,并去除前后的空格String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = loader.getResources(fullName);} catch (IOException x) {//...}//....}}private S nextService() {String cn = nextName;nextName = null;Class<?> c = null;try {//反射加载类 将上面获取的url进行转换为对象类c = Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {}try {//实例化S p = service.cast(c.newInstance());//放进缓存providers.put(cn, p);return p;} catch (Throwable x) {//..}//..}}}
三、总结
SPI机制能够在service provider和service user之间实现解耦,在不用修改源码的情况下可以实现扩展,对原来的代码没有侵入性,符合开闭原则。




