双亲委派和破坏双亲委派

对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性。
如果不是同一个类加载器加载,即时是相同的class文件,也会出现判断不相同的情况,从而引发一些意想不到的情况,为了保证相同的class文件,在使用的时候,是相同的对象,jvm设计的时候,采用了双亲委派的方式来加载类。

双亲委派

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

系统加载器

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

破坏双亲委派

为什么需要破坏双亲委派?

因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派,这里仅仅是举了破坏双亲委派的其中一个情况。

破坏双亲委派的实现

结合Driver来看一下在spi(Service Provider Inteface)中如何实现破坏双亲委派。
先从DriverManager开始看,平时我们通过DriverManager来获取数据库的Connection:

1
2
String url = "jdbc:mysql://localhost:3306/testdb";
Connection conn = java.sql.DriverManager.getConnection(url, "root", "root");

在调用DriverManager的时候,会先初始化类,调用其中的静态块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
...
// 加载Driver的实现类
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
}
return null;
}
});
...
}

重点来看一下ServiceLoader.load(Driver.class):

1
2
3
4
5
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前线程中的上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

可以看到,load方法调用获取了当前线程中的上下文类加载器,那么上下文类加载器放的是什么加载器呢?

1
2
3
4
5
6
7
8
9
10
public Launcher() {
...
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
...
}

在sun.misc.Launcher中,我们找到了答案,在Launcher初始化的时候,会获取AppClassLoader,然后将其设置为上下文类加载器,而这个AppClassLoader,就是之前上文提到的系统类加载器Application ClassLoader,所以上下文类加载器默认情况下就是系统加载器。
继续来看下ServiceLoader.load(service, cl):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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");
// ClassLoader.getSystemClassLoader()返回的也是系统类加载器
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}

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

上面这段就不解释了,比较简单,然后就是看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
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
private class LazyIterator implements Iterator<S>{
// ServiceLoader的iterator()方法最后调用的是这个迭代器里的next
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);
}
}

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
}

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

private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 在classpath下查找META-INF/services/java.sql.Driver名字的文件夹
// private static final String PREFIX = "META-INF/services/";
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;
}
}

整个流程

  1. Launcher初始化,将AppClassLoader设置为ContextClassLoader
  2. 获取ContextClassLoader调用ServiceLoader.load(xxx.class,loader)
  3. 初始化ServiceLoader,LazyIterator
  4. 调用LazyIterator.hasNext(),在classpath下的META-INF/services/查找对应的xx接口的文件
  5. 调用LazyIterator.next(),用APPClassLoader和全限定名来加载实现类