avatar

目录
Java SPI机制

SPI即Service Provider Interface, 即服务提供者接口. 是由JDK内置提供的一种服务接口发现机制,主要是方便开发人员进行接口扩展或者组件替换,一般多用于框架开发中.

1.SPI的原理

SPI接口发现机制功能的实现主要是通过java.util.ServiceLoader 这个类来完成.

首先看一眼这个类都有哪些成员变量

java
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
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
// 用来定位、装载和实例化providers接口的类加载器
private final ClassLoader loader;

// The access control context taken when the ServiceLoader is created
// 创建ServiceLoader时的上下文安全控制器
private final AccessControlContext acc;

// Cached providers, in instantiation order
// providers缓存,按实例化顺序存放装载进来的providers
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// The current lazy-lookup iterator
// 懒查找迭代器
private LazyIterator lookupIterator;
}

ServiceLoader 会去/META-INF/service/目录底下读取所有的配置文件,将文件URL存放到一个枚举集合Enumeration<URL> 中,迭代这个集合,用文件流的方式读取每一个URL代表的文件,在每个文件中逐行读取,取得要装载类的全限定名,如果类名是合规的,再用

java
1
Class.forName(className, classLoader)

的方式获得要装载的类的字节码对象,再调用反射方法 newInstance() 来进行类的实例化,最后将其放入providers缓存中.

2. 装载类的具体过程

参照着源码,梳理一下具体过程

  1. 应用程序调用ServiceLoader.load()方法
    ServiceLoader.load()方法内先创建一个新的ServiceLoader,并初始化该类中的成员变量.
java
1
2
3
4
5
6
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();
}
  1. 接着调用reload()方法,reload()方法主要做了两件事,一是清空原有的providers缓存,二是实例化一个查找迭代器 LazyIterator.
java
1
2
3
4
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
  1. LazyIterator类事实上是内部 Iterator<S> 接口的一个私有化自我实现. 当调用方调用到IteratorhasNext()方法时,实际上会调用到LazyIterator<S>hasNextService() 方法.
java
1
2
3
4
5
6
7
8
9
10
11
12
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); // 调用到hasNextService()

}
};
return AccessController.doPrivileged(action, acc);
}
}
  1. hasNextService() 方法里边,就会取读取/META-INF/service 目录底下的配置文件,并将文件以URL的形式存放在一个枚举集合中. 再通过 parse() 方法读取每一个URL文件中的类名(逐行读取),并将类名存放在Iterator 中返回.
java
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
if (configs == null) {      // configs 就是存放文件URL的枚举集合
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()); // parse() 方法中会针对每一个文件URL,按行读取到文件中的类名最终返回一个集合
}
}

private Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
  1. 当调用方调用到 next() 方法时,实际上调用的是LazyIterator 的 nextService() 方法, 在这个方法中,会迭代Iterator集合,依次取出类名通过反射来进行类的装载和实例化. 对应的源码如下:
java
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
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,
"Provider " + cn + " not found");
}
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);
}
throw new Error(); // This cannot happen
}

文章作者: JanGin
文章链接: http://jangin.github.io/2021/03/04/Java-SPI/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 JanGin's BLOG

评论