SPI和API
问题1
面向的对象的设计里,提倡模块之间基于接口编程。调用方、接口、实现方关系如下

这个时候接口应该在哪定义呢?有三种情况:
- 接口位于实现方所在的包中:称为API(Application Programming Interface,应用程序接口)。
- 调用方直接依赖三方库
- 从名字上理解,API是给客户端调用的接口
- 从时间上讲,先有实现方和接口,再有调用方,实现方较固定,调用方可变。
- 常用于SDK开发,接口由三方库开发者提供,并可能提供了多种实现,调用方只负责使用。(如果调用方能够自己实现功能,何必再使用三方库呢?)
- 当然有些框架也会提供SPI接口,调用方有需求的时候也可以自己实现,替换SDK中的实现,如自定义GlideModule。
- 接口位于调用方所在的包中:称为SPI(Service Provider Interface,服务提供接口)
- 插件依赖调用方接口
- 从名字上理解,SPI服务接口是给服务端实现的接口
- 从时间上讲,先有调用方和接口,再有具体的实现方,实现方可变,调用方较固定
- 常用于插件开发,调用方先定义好接口,并写好调用逻辑,由插件实现接口。如自定义注解处理器、插件换肤。
- 接口位于独立的包中:接口既可以作为API,也可以作为SPI
- 在Clean或DDD架构中,业务层对外层提供接口,被表现层或应用层调用(API),同时外层需要实现业务层提供的接口(SPI),被业务驱动。
注:
- 不管是SPI或API,接口都可以组织到独立的包中,真正的区别是由调用方提供还是由实现方提供。
- SPI和API本质上是一个谁迁就谁的问题:
- 使用API,对调用方来说,不同的实现方API接口可能不一致,代码中需要写条件分支判断。(例如代码中同时用了ImageLoader和Glide来加载图片)
- 使用SPI,就是由调用方来决定接口行为,不同的实现方都需要遵循这个接口定义。
- 广义上讲API是一种提供给外部调用自身的方式,不单指Java语言中的接口,例如我们常说的Restful API,实际上是网络请求的url
- 广义上讲SPI是一种让外部能够按照自己定制的规则实现功能的方式,不单指Java语言中的接口,也指设计规范
问题2
在SPI的情况下,插件需要依赖调用方,调用方也需要依赖插件具体实现(new实例对象)。是否会产生依赖冲突呢,三者的依赖关系应该是怎么样的?
从编码上讲会存在依赖冲突,当然这个时候调用方不一定是直接依赖插件,也可以通过动态注册、加载,如自定义ClassLoader、动态链接库等。
问题3
抛开依赖冲突的问题,如果接口定义在调用方的包中,插件依赖的时候会把调用方的逻辑也依赖进来,而插件只是负责实现接口而已,这是否有必要呢?
有一个原则可以回答这个问题
依赖倒置:高层模块不依赖底层模块,两者都应该依赖于抽象。抽象不应该依赖具体,具体应该依赖抽象
实际上就是说把接口(抽象层)抽出来,调用方和实现方都依赖接口,也就是上面的第3种情况。
服务发现机制
SPI中调用方提供接口,调用方直接实例化对象,存在问题:
- 调用方先定义好接口,无法写逻辑,需要等到实现方实现接口之后再来添加逻辑。(当然也可以先定义一个默认的Fake实现类)
- 如果要替换一种实现,需要修改调用方的代码,不符合可插拔的原则
为了解决上面的问题,让调用方不需要指定具体模块,需要一种服务注册和发现的机制:服务端注册了服务之后,客户端可以找到对应的服务。

服务发现是IoC(Inversion of Control,控制反转)思想的一种实现,将初始化实例的控制权移到了程序之外。
Tips:IoC主要有两种实现:
- 服务提供模式:从外部服务容器抓取依赖对象
- 依赖注入:以参数的形式注入依赖对象
控制反转(或服务发现)有以下优势:
- 在外部注入或配置依赖项,因此我们可以重用这些组件。当我们需要修改依赖项的实现时,只需要修改配置文件;
- 可以配置依赖项的模拟实现(Fake),让代码测试更加容易。
Java SPI机制
JDK6中引入了ServiceLoader,通过配置文件来装载指定的服务。也叫做Java SPI机制(这里要和上文所说的SPI接口区分开来)
ServiceLoader是Java提供的一种SPI机制,但服务发现机制不是Java特有的
类似地,在Android中,通过Manifest注册组件,让Launcher能够找到应用程序的入口,或者让AMS能够启动应用内的Activity、Service,发送广播等。
Activity可以看作Android系统提供的SPI,Manifest是一种服务发现机制,应用程序需要按规范继承、注册,以便AMS能够对Activity进行管理。
使用方式
- 客户端定义服务接口
- 服务端实现该接口
- 服务端在
META-INF/services目录下新建一个以服务接口命名的文件,文件内填入该服务接口的具体实现类(完整类名,可以填多个)
- 客户端使用
ServiceLoader加载配置文件中的具体实现类并返回实例化对象。存在多个实现类,通过迭代器iterator访问
Tips:这里客户端指接口调用方,服务端指接口实现方
第4步除了使用ServiceLoader去加载配置文件外,我们也可以自己读取文件,通过反射创建实例。实际上ServiceLoader正是帮我们做了这件事
第3步除了手动创建之外,还可以使用AutoService库帮我们自动生成文件:通过APT解析@AutoSerivce自动生成文件。
- 添加依赖包和对应的注解处理器
1 2 3 4
| dependencies { compileOnly "com.google.auto.service:auto-service-annotations:1.0" annotationProcessor "com.google.auto.service:auto-service:1.0" }
|
- 使用
@AutoService(Processor.class),即可自动生成SPI文件,如下
1 2 3 4 5
|
@AutoService(Processor.class) public final class AptProcessor extends AbstractProcessor { }
|
原理
- 首先看
Service.load源码,返回ServiceLoader对象:可以看到这个时候还没加载实现类,一个服务接口对应一个ServiceLoader实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); }
public static <S> ServiceLoader<S> load(Class<S> service) { 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); }
|
- ServiceLoader构造函数:保存服务接口的类对象,并创建了一个
LazyIterator对象,迭代器中暂时只是保存了service类对象和类加载器。
1 2 3 4 5 6 7 8 9 10 11 12
| private ServiceLoader(Class<S> var1, ClassLoader var2) { this.service = (Class)Objects.requireNonNull(var1, "Service interface cannot be null"); this.loader = var2 == null ? ClassLoader.getSystemClassLoader() : var2; this.acc = System.getSecurityManager() != null ? AccessController.getContext() : null; this.reload(); } public void reload() { this.providers.clear(); this.lookupIterator = new ServiceLoader.LazyIterator(this.service, this.loader); }
|
- 到这里还没开始加载具体的实现类,继续看迭代器中关键的
nextService方法:可以看到使用Class.fromName反射加载了nextName类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| private S nextService() { if (!this.hasNextService()) { throw new NoSuchElementException(); } else { String var1 = this.nextName; this.nextName = null; Class var2 = null; try { var2 = Class.forName(var1, false, this.loader); } catch (ClassNotFoundException var5) { ServiceLoader.fail(this.service, "Provider " + var1 + " not found"); } if (!this.service.isAssignableFrom(var2)) { ServiceLoader.fail(this.service, "Provider " + var1 + " not a subtype"); } try { Object var3 = this.service.cast(var2.newInstance()); ServiceLoader.this.providers.put(var1, var3); return var3; } catch (Throwable var4) { ServiceLoader.fail(this.service, "Provider " + var1 + " could not be instantiated", var4); throw new Error(); } } }
|
nextName从哪来的呢,继续看hasNextService方法:可以看到读取了META-INF/services/下的服务配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| private boolean hasNextService() { if (this.nextName != null) { return true; } else { if (this.configs == null) { try { String var1 = "META-INF/services/" + this.service.getName(); if (this.loader == null) { this.configs = ClassLoader.getSystemResources(var1); } else { this.configs = this.loader.getResources(var1); } } catch (IOException var2) { ServiceLoader.fail(this.service, "Error locating configuration files", var2); } }
while(this.pending == null || !this.pending.hasNext()) { if (!this.configs.hasMoreElements()) { return false; } this.pending = ServiceLoader.this.parse(this.service, (URL)this.configs.nextElement()); } this.nextName = (String)this.pending.next(); return true; } }
|
- 继续看
ServiceLoader的iterator方法,对懒加载迭代器进行了包装,首先返回已经实例化过并缓存下来的服务对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public Iterator<S> iterator() { return new Iterator<S>() { Iterator<Entry<String, S>> knownProviders; { this.knownProviders = ServiceLoader.this.providers.entrySet().iterator(); }
public boolean hasNext() { return this.knownProviders.hasNext() ? true : ServiceLoader.this.lookupIterator.hasNext(); }
public S next() { return this.knownProviders.hasNext() ? ((Entry)this.knownProviders.next()).getValue() : ServiceLoader.this.lookupIterator.next(); }
public void remove() { throw new UnsupportedOperationException(); } }; }
|
总结一下:ServiceLoader本质就是实现了一个迭代器,读取META-INF/services下的文件,通过ClassLoader加载文件中的接口实现类,放到懒加载迭代器中,访问的时候才加载类并实例化对象。
有几个注意的点:
- 使用了懒加载创建服务实例
- 使用
LinkedHashMap缓存创建过的provider实例
- 服务实现类必须实现服务接口:
if (!service.isAssignableFrom(c));
- 服务实现类需包含无参的构造器,LazyInterator 是反射创建服务对象的:
S p = service.cast(c.newInstance());
- 配置文件需要使用 UTF-8 编码:
parse方法中new BufferedReader(new InputStreamReader(in, "utf-8"))。
- 存在多个实现类的时候,并不一定需要全部使用,只能迭代筛选合适的实现类,因此可以尽可能把常用的放到配置文件前面,避免加载不常用的类
- 服务无法注销remove,只能当没有引用时被GC回收,
ServiceLoader提供了reload方法清除自身的缓存
案例
- JDBC加载不同的数据库驱动,服务接口为
java.sql.Driver,由不同的数据库驱动要实现该服务接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.mysql.jdbc; public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }
|
DriverManager源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class DriverManager { static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { ServiceLoader var1 = ServiceLoader.load(Driver.class); Iterator var2 = var1.iterator(); } public static Connection getConnection(String var0, String var1, String var2) throws SQLException { Iterator var5 = registeredDrivers.iterator(); while(var5.hasNext()) { DriverInfo var6 = (DriverInfo)var5.next(); if (isDriverAllowed(var6.driver, var3)) { try { println(" trying " + var6.driver.getClass().getName()); Connection var7 = var6.driver.connect(var0, var1); if (var7 != null) { println("getConnection returning " + var6.driver.getClass().getName()); return var7; } } } } } }
|
可以看到mysql的jdbc驱动包中确实存在服务配置文件

如果三方数据库驱动没有配置服务文件(例如Oracle数据库),就需要我们自己通过Class.forName去加载驱动类了
- APT加载不同的注解处理器。服务接口为
javax.annotation.processing.Processor,由不同的注解处理器实现该服务接口
结语
本来是打算介绍JavaSPI机制,放到Java分类下的。但是研究之后发现更应该归属到架构分类。
了解SPI和API区别,有助于我们更好的理解IoC、依赖倒置原则、DDD(Domain Driven Design,领域驱动设计)等。
参考资料: