SPI

SPI(Service Provider Interface) 是 JDK 6(1.6)引入的一种服务发现机制,这里 Service 通常是一个 interfaceclass,它定义了用户所能访问的接口; Provider 通常是 Service 实现类、子类或者定义了 provider 静态方法的任意类(JDK 9)。

SPI

面向对象程序设计中,推荐模块之间基于接口,而不是具体的实现类进行交互,避免暴露具体的实现类及其装配过程(SOLID原则)。但如果只从程序设计角度看,还不足以支撑 JDK 引入 SPI,毕竟面向接口编程也不是什么难事,况且 SPI 还会有反射带来的性能损耗。分析 JDK 内置 SPI 的原因,可以从 JDK 内置的支持 SPI 的包去分析:

可以看到 JDK 内置 SPI 都有一个共同特点,那就是只要将实现类所在的 jar 包安装到 classpath 或 modulepath 就可以自动被 JDK 加载,而不用调整代码。换言之,这些 jar 就是插件,在程序发布后还能根据需要安装不同插件,进而调整程序行为。

ServiceLoader

SPI 描述了服务提供与发现的机制,ServiceLoader 则实现了对应的策略,提供切实可以的方式去定义与实现自己的 SPI。下面通过官方文档中的例子进行简要的说明。

定义 SPI

SPI 接口除不能描述服务提供者如何实现外,有两个一般准则:

  • 根据需要定义足够的接口。
  • 接口需要服务提供者是直接实现接口还是作为 proxyfactory 间接提供服务(有点绕,其实是对 SPI 接口的一个分类,下面讨论)。

下面是 ServiceLoader 文档中的接口定义:

1
2
3
4
5
package com.example;
public interface CodecFactory {
Encoder getEncoder(String encodingName);
Decoder getDecoder(String encodingName);
}

定义了编解码器工厂接口。一般来讲,编解码器的实例化开销都比较高(分配缓冲区),所以 getEncodergetDecoder 提供了一个 encodingName 来指定需要的编解码器,如果是不支持的编解码格式,则直接返回 null,避免实例化所有编解码器后再一个个判断是否支持。这就是上面准则 2对应的 SPI 接口类型。

实现、部署 SPI

在 JDK 9 支持 module 后,又新增了一种实现 SPI 接口的方式,直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Since JDK 6
package com.example.impl;

public class StandardCodecs implements CodecFactory {
Encoder getEncoder(String encodingName) { ... }
Decoder getDecoder(String encodingName) { ... }
}

// Since JDK 9
package com.example.impl;

public class ExtendedCodecsFactory {
public static StandardCodecs provider() { ... }
}

StandardCodecs 直接实现 CodecFactory 并实现其中的两个方法,使用默认构造器初始化。JDK 9 之后我们可以使用声明了 “public static no-args method named ‘provider’ with a return type of Service” 的类作为服务提供者。

在 JDK 9之前,以 jar 文件方式发布服务提供者还需要提供一个 provider-configuration 文件,列举所有服务提供者,使服务提供者能够被识别,配置文件存放在 META-INF/services 下面,文件名是 SPI 接口的全限定名,并写入服务提供者的全限定名作为内容:

1
2
#  META-INF/services/com.example.CodecFactory
com.example.impl.StandardCodecs

每一行就代表一个服务提供者,每一行 “#” 及后面的字符都会被忽略。

如果以 module(JDK 9) 方式 发布服务提供者,则需要在 module declaration 使用 provides 指令声明服务提供者:

1
2
3
4
5
6
module your.module.name {
...
provides com.example.CodecFactory with com.example.impl.StandardCodecs;
provides com.example.CodecFactory with com.example.impl.ExtendedCodecsFactory;
...
}

声明方式相比 jar 的 provider-configuration 要更集中一些。

使用 SPI

1
2
3
4
5
6
7
8
ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
for(CodecFactory factory: loader) {
Encoder enc = factory.getEncoder("PNG");
if(enc != null) {
// use enc to encode a PNG file
break;
}
}

如果使用了 module,则还需要声明当前 module 为 Service 的消费者:

1
2
3
module your.module.name {
uses com.example.impl.StandardCodecs
}

ServiceLoader 实现

不管是 JDK 9 之前还是之后,ServiceLoader 都是通过反射调用无参构造器或 provider 方法创建服务提供者实例。根据上面部署的方式,要拿到服务提供者,只需要读取到配置信息就可以了,即 ServiceLoader 的核心代码是配置信息的读取(个人看法):

核心代码在 ModuleServicesLookupIterator(JDK 9) 和 LazyClassPathLookupIterator 中,前者优先级更高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ServiceLoader#LazyClassPathLookupIterator#nextProviderClass
private Class<?> nextProviderClass() {
// configs: Enumeration<URL>
if (configs == null){
// PREFIX: META-INF/services/
String fullName = PREFIX + service.getName();
configs = loader.getResources(fullName);
}
// pending: Iterator<String>
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return null;
}
pending = parse(configs.nextElement());
}
String cn = pending.next();
return Class.forName(cn);
}

ServiceLoader 从 module 获取服务提供者相关部分比较复杂,所以只看解析部分吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ServiceLoader#ModuleServicesLookupIterator#loadProvider
private Provider<S> loadProvider(ServiceProvider provider) {
String cn = provider.providerName();
Class<?> clazz = Class.forName(module, cn);

// if provider in explicit module then check for static factory method: provider
if (inExplicitModule(clazz)) {
Method factoryMethod = findStaticProviderMethod(clazz);
if (factoryMethod != null) {
Class<?> returnType = factoryMethod.getReturnType();

Class<? extends S> type = (Class<? extends S>) returnType;
return new ProviderImpl<S>(service, type, factoryMethod, acc);
}
}
// no factory method
Class<? extends S> type = (Class<? extends S>) clazz;
Constructor<? extends S> ctor = getConstructor(clazz);
return new ProviderImpl<S>(service, type, ctor, acc);
}

JDK 9 module 加载之后,多个 module 会组成一个 module layer,一个 module layer 可以映射到多个 class loader,也即可以被多个 class loader 访问到里面的类。那么服务提供者会从当前 class loader 可访问的 module 开始搜索,然后从其 parent class loader 继续搜索直到 bootstrap class loader。可以看到 module 与相比 provider-configuration ,没有了读文件操作,理论上性能更优。

其它

网上一些文章都指出了 ServiceLoader 遍历性能会有点差,这中间的瓶颈在文件 IO(JDK 9 module 中无),因为需要遍历 classpath。当发布后的程序不要支持插件这种动态能力,这种无意义的性能开销就需要被规避。常用的优化手段有缓存和静态服务注册表,后者可能就不会使用 ServiceLoader 实现了。

至于 ServiceLoader 不是线程安全的缺点,很多类库都不是线程安全的好伐,这个缺点太牵强了!!!

参考文章

ServiceLoader

Java Service Provider Interface

Introduction to the Service Provider Interfaces

Understanding Java 9 Modules