SPI机制
# 什么是SPI
SPI(Service Provider Interface),是JDK内置的一种服务发现机制,用来将调用方与服务提供方解耦,通常用于框架开发、组件替换。比如Java
的java.sql.Driver
接口,其他数据库厂商MySQL、PostgreSQL提供不同的实现。通过SPI机制,我们在使用数据库驱动时不需要显示指定某个厂家的,只需要引入驱动jar包,系统即可完成加载。
# 代码示例
# 服务接口定义
package cn.wangjie.learn.spi;
public interface IShout {
void shout();
}
1
2
3
4
5
2
3
4
5
# 服务提供方实现接口
package cn.wangjie.learn.spi;
public class Cat implements IShout{
@Override
public void shout() {
System.out.println("cat shout:喵喵喵");
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
package cn.wangjie.learn.spi;
public class Dog implements IShout{
@Override
public void shout() {
System.out.println("dog shout:汪汪汪");
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 服务提供方在resources
下新建META-INF/services/
,使用接口全限定名新建文件cn.wangjie.learn.spi.IShout
cn.wangjie.learn.spi.Dog
cn.wangjie.learn.spi.Cat
1
2
2
# 调用方调用
package cn.wangjie.learn.spi;
import java.util.*;
public class SPIMain {
public static void main(String[] args) {
ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);
for (IShout shout : shouts) {
shout.shout();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 运行打印日志
dog shout:汪汪汪
cat shout:喵喵喵
1
2
2
# 服务发现原理
查看ServiceLoader
类结构
进入ServiceLoader.load
方法
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取当前线程的类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//该方法return new ServiceLoader<>(service, loader)
return ServiceLoader.load(service, cl);
}
1
2
3
4
5
6
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
2
3
4
5
6
2
3
4
5
6
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
private LazyIterator lookupIterator;
public void reload() {
providers.clear();
//提供一个懒加载的迭代器
lookupIterator = new LazyIterator(service, loader);
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
ServiceLoader
实现了Iterable
接口
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();
}
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
当获取服务提供类时,通过迭代器遍历
先从providers
中获取已经加载过的,之后从lookupIterator
中加载
查看LazyIterator
类
private static final String PREFIX = "META-INF/services/";
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) {
try {
//加载META-INF/services/+全限定名的文件 多个服务提供方可能在不同的jar包,可能会加载多个资源文件
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,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//实例化服务提供类 服务提供类需要有无参构造方法
S p = service.cast(c.newInstance());
//加载过的放到list中
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() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
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
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
JDK
自带的SPI
主要是通过加载META-INF/services/+全限定名
的文件获取服务提供类,然后迭代器遍历每一个服务提供类,通过反射实例化服务提供类。
# SPI机制的缺陷
- 只能通过迭代器遍历,逐个实例化服务提供类
ServiceLoader
非线程安全类
编辑 (opens new window)
上次更新: 2023/04/09, 16:34:32