javaSPI机制

service provider interface

1
2
3
4
5
// 通过遍历即可获取到对应的class,那么原理是什么呢?
Iterator<ABC> iterator = ServiceLoader.load(ABC.class).iterator();
while (iterator.hasNext()) {
ABC next = iterator.next();
}

原理

java规定spi的配置文件都在这个目录META-INF/services/
该目录下可以有多个文件,文件的名称必须以class全类名命名。通过ServiceLoader类,读取名字为spi类的全名称的文件
内容为多行,一行为一个class的全类名。该class为spi的实现类。

ServiceLoader入口

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

public final class ServiceLoader<S> implements Iterable<S> {

private static final String PREFIX = "META-INF/services/"; // 扫描的目录

private final Class<S> service; // spi 要加载的class

private final ClassLoader loader;// classLoader,默认为当前线程的classLoader

private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 已加载过的SPI都会缓存

private LazyIterator lookupIterator; // 真正加载的class

private ServiceLoader(Class<S> svc, ClassLoader cl) {
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
lookupIterator = new LazyIterator(service, loader);
}
...
// 入口
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}

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();
}
...
};
}
}

LazyIterator 真正的加载类

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
private class LazyIterator implements Iterator<S> {
...
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;// 已扫描的url
Iterator<String> pending = null; // 当前扫描的url资源中的spi文本(className为多行)
String nextName = null; //下一个spi实现类的名称
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}

// 迭代器的实现
public boolean hasNext() {
...
configs = configs != null ? configs : loader.getResources("META-INF/services/" + service.getName());
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}

pending = parse(service, configs.nextElement());
}
// 直接返回
nextName = pending.next();
return true;
...
}

// 迭代器的实现
public S next() {
...
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
}
// 该不是spi接口的实现,乱配置的就直接抛出异常
if (!service.isAssignableFrom(c)) {
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);
}
...
}
...
}

总结

本文以jdk1.8来分析,其他版本略有不同。总的来说就是获取资源META-INF/services/目录下的文件名与spi的class名称一致的文件。 读取里面的实现类,然后通过反射按需实例化并缓存。
但是缓存有局限性哦,只能针对同一个ServiceLoader对象多次产生的迭代器有效哦。