learn and grow up

SPI是否破坏了双亲委派?

字数统计: 908阅读时长: 3 min
2020/08/01 Share

写在前面

​ 之前看很多文章讲到class加载的双亲委派机制,百分之九十概率会提到SPI且说SPI破坏了双亲委派机制,之前也只是糊里糊涂的觉得是这么个回事,但是最近在看jvm。回过头来看,自己觉得SPI其实并没有破坏双亲委派。

双亲委派

含义:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。

​ 这里有几个流程要注意一下:

  1. 子类先委托父类加载
  2. 父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类
  3. 子类在收到父类无法加载的时候,才会自己去加载

jvm提供了三种系统加载器:

  1. 启动类加载器(Bootstrap ClassLoader):C++实现,在java里无法获取,负责加载/lib下的类。
  2. 扩展类加载器(Extension ClassLoader): Java实现,可以在java里获取,负责加载/lib/ext下的类。
  3. 系统类加载器/应用程序类加载器(Application ClassLoader):是与我们接触对多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。

SPI

这里暂时不讨论为什么要用SPI,只是讨论SPI的实现原理,来看看他到底是不是破坏了双亲委派

直接上代码,这里使用SPI中的java.util.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
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
//入口
//java.util.ServiceLoader#load(java.lang.Class<S>)
public static <S> ServiceLoader<S> load(Class<S> service) {
//一开始就获取了应用的APClassLoader
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");
//如果空则使用jvm默认classloader
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}

public void reload() {
providers.clear();
//lookupIterator就是用来扫描及加载类的
lookupIterator = new LazyIterator(service, loader);
}

//LazyIterator是个内部类,用来懒加载,逻辑比较简单
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//PREFIX="META-INF/services/"
String fullName = PREFIX + service.getName();
if (loader == null)
//默认classLoader
configs = ClassLoader.getSystemResources(fullName);
else
//SPI走这里
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 {
//重点在这里,可以看到他是用到forName这个进行clas
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
}

​ 通过上面代码可以看到,最终的加载class还是利用原生的classLoader进行了class的加载,只是双亲委派的代表是Classloader的load,而这里使用了forName而已。所以并没有打破双亲委派。

未完待续……

CATALOG
  1. 1. 写在前面
  2. 2. 双亲委派
  3. 3. SPI