Java SPI
Java SPI
SPI 背景
怎么获取接口的实现类?
- 全局扫描全部的 Class,然后判断是否实现了某个接口?代价太大,一般不会这么做
- 一种合适的方式就是使用配置文件,把实现类名配置在某个地方,然后读取这个配置文件,获取实现类名。JDK 给我们提供的 ServiceLoader 就是这种方式,通过读取
resources/META-INFO/services
的配置。
SPI 介绍
SPI,全称 Service Provider Interfaces
,服务提供接口。是 Java 提供的一套供第三方实现或扩展使用的技术体系。主要通过解耦服务具体实现以及服务使用,使得程序的可扩展性大大增强,甚至可插拔。
基于服务的注册与发现机制,服务提供者向系统注册服务,服务使用者通过查找发现服务,可以达到服务的提供与使用的分离,甚至完成对服务的管理。
完成分离后的服务,使得服务提供方的修改或替换,不会给服务使用方带来代码上的修改,基于面向接口的服务约定,提供方和使用方各自直接面向接口编程,而不用关注对方的具体实现。同时,服务使用方使用到服务时,也才会真正意义上去发现服务,以完成服务的初始化,形成了服务的动态加载。
ServiceLoader
JDK 中,基于 SPI 的思想,提供了默认具体的实现,ServiceLoader。利用 JDK 自带的 ServiceLoader,可以轻松实现面向服务的注册与发现,完成服务提供与使用的解耦。
ServiceLoader 使用
1
2
3
4
1. 创建一个接口文件
2. 在resources资源目录下创建`META-INF/services`文件夹
3. 在services文件夹中创建文件,以接口全名命名
4. 创建接口实现类
- 定义接口
me.hacket.javalib.IMyServiceProvider
1
2
3
4
package me.hacket.javalib;
public interface IMyServiceProvider {
String getName();
}
- 在 resources 资源目录下创建
META-INF/services
文件夹,以接口全名命名,并将实现类的全路径配置进去
1
2
me.hacket.javalib.MyServiceProviderImpl1
me.hacket.javalib.MyServiceProviderImpl2
- 接口的实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyServiceProviderImpl1 implements IMyServiceProvider {
@Override
public String getName() {
return "name:ProviderImpl1";
}
}
public class MyServiceProviderImpl2 implements IMyServiceProvider {
@Override
public String getName() {
return "name:ProviderImpl2";
}
}
- 测试
1
2
3
4
5
6
7
8
public class TestSPIClass {
public static void main(String[] args) {
ServiceLoader<IMyServiceProvider> serviceLoader = ServiceLoader.load(IMyServiceProvider.class);
for (IMyServiceProvider item : serviceLoader) {
System.out.println(item.getName() + ": " + item.hashCode());
}
}
}
ServiceLoader 原理
从 ServiceLoader#load
开始分析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
reload();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
最后调用的是 reload 方法,清空 providers,初始化一个 LazyIterator
然后一般会通过 ServiceLoader 对象遍历,那么就会调用其 iterator 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext()) // 先从缓存中找,有的话直接返回
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext()) // 先从缓存中找,有的话直接返回
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
实际上是调用的 lookUpIterator 各个方法,从前面我们知道这是一个 LazyIterator。
遍历时会通过 hasNext 判断是否还有下一个元素,next 获取下一个元素。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) { // 枚举,就是META-INF/services/接口全名配置的接口所有实现类的全路径
try {
// META-INF/services/接口全名
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService()) throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader); // 通过获取到的接口实现类的全路径反射
} catch (ClassNotFoundException x) {
fail(service,
// Android-changed: Let the ServiceConfigurationError have a cause.
"Provider " + cn + " not found", x);
// "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
// Android-changed: Let the ServiceConfigurationError have a cause.
ClassCastException cce = new ClassCastException(
service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
fail(service,
"Provider " + cn + " not a subtype", cce);
// fail(service,
// "Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance()); // 反射实例化
providers.put(cn, p); // 缓存起来
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
return hasNextService();
}
public S next() {
return nextService();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
- 在
META-INF/services/全路径接口
中配置了接口所有的实现类 - 通过反射获取所有的实现类,并缓存起来,下次直接返回
AutoService
https://github.com/google/auto/tree/master/service
1
2
3
4
5
6
7
8
package foo.bar;
import javax.annotation.processing.Processor;
@AutoService(Processor.class)
final class MyProcessor implements Processor {
// …
}
会自动生成 META-INF/services/javax.annotation.processing.Processor
,文件包含
1
foo.bar.MyProcessor
应用案例
Processor
编译时注解,用到了 ServiceLoader,javac 会自动加载找到 Processor