Java SPI 机制

SPI 全称 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。简单讲就是为某个接口寻找服务实现的机制。

java spi

Java SPI 实际上是基于接口的编程+策略模式+配置文件组合实现的动态加载机制,可以轻松实现面向服务的注册与发现,完成服务提供与使用的解耦。

完成分离后的服务,使得服务提供方的修改或替换,不会给服务使用方带来代码上的修改,基于面向接口的服务约定,提供方和使用方各自直接面向接口编程,而不用关注对方的具体实现。同时,服务使用方使用到服务时,也才会真正意义上去发现服务,以完成服务的初始化,形成了服务的动态加载。

使用 Java SPI,需要遵循如下约定:

  • 当服务提供者提供了接口的一种具体实现后,在 jar 包的 META-INF/services 目录下创建一个以 接口全限定命 为命名的文件,内容为实现类的全限定名;
  • 接口实现类所在的 jar 包放在主程序的 classpath 中;
  • 主程序通过 java.util.ServiceLoder 动态装载实现模块,它通过扫描 META-INF/services 目录下的配置文件找到实现类的全限定名,通过反射实例化类对象,并加载到 JVM;
  • SPI 的实现类必须携带一个不带参数的构造方法;

示例

1
2
3
4
5
6
7
8
9
10
11
12
└─src
├─com
│ └─spi
│ IInterface.java
│ ImplA.java
│ ImplB.java
│ Main.java

└─resources
└─META-INF
└─services
com.spi.IInterface

com.spi.IInterface 文件内容为:

1
2
com.spi.ImplA
com.spi.ImplB
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
public interface IInterface {
String name();
}

public class ImplA implements IInterface {
@Override
public String name() {
return "ImplA";
}
}

public class ImplB implements IInterface {
@Override
public String name() {
return "ImplB";
}
}

public class Main {
public static void main(String[] args) {
ServiceLoader<IInterface> serviceLoader = ServiceLoader.load(IInterface.class);
for (IInterface service : serviceLoader) {
System.out.println(service.name());//输出: ImplA ImplB
}
}
}

//文件 com.spi.IInterface 包含如下内容
com.spi.ImplA
com.spi.ImplB

源码分析

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
public final class ServiceLoader<S> implements Iterable<S> {
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;

public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) {
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
// 检查文件,不符合抛异常
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}

private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
ArrayList<String> names = new ArrayList<>();
InputStream in = u.openStream();
BufferedReader r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
return names.iterator();
}

public Iterator<S> iterator() {
return new Iterator<S>() {
public boolean hasNext() {
return lookupIterator.hasNext();
}

public S next() {
return lookupIterator.next();
}

};
}

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}

// Private inner class implementing fully-lazy provider lookup
private class LazyIterator implements Iterator<S> {
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
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() {
String cn = nextName;
nextName = null;
Class<?> c = Class.forName(cn, false, loader);
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
}
}
}
  1. ServiceLoader.load() 方法创建 ServiceLoader 对象;
  2. 通过 foreach 遍历 ServiceLoader 时调用 ServiceLoader#iterator() 方法;
  3. 实际是通过私有内部类 LazyIterator 代理遍历解析 Service;
  4. 解析过程是找到 META-INF 目录下的接口文件,打开该文件流,一行一行的读取,获取实现类字符串,保存到 names 集合中;
  5. 遍历 names 集合,通过反射实例化 Service,并保存在 providers 缓存中;

参考

[1] Java SPI 机制 - ServiceLoader
[2] Java SPI 机制:ServiceLoader 实现原理及应用剖析