24.2 理解ClassLoader

24.2 理解ClassLoader

类ClassLoader是一个抽象类,Application ClassLoader和ExtensionClassLoader的具体实现类分别是sun.misc.Launcher$AppClassLoader和sun.misc.Launcher$ExtClassLoader, Bootstrap ClassLoader不是由Java实现的,没有对应的类。

每个Class对象都有一个方法,可以获取实际加载它的ClassLoader,方法是:

1
public ClassLoader getClassLoader()

ClassLoader有一个方法,可以获取它的父ClassLoader:

1
public final ClassLoader getParent()

如果ClassLoader是Bootstrap ClassLoader,返回值为null。比如:

1
2
3
4
5
6
7
8
9
10
public class ClassLoaderDemo {
public static void main(String[] args) {
ClassLoader cl = ClassLoaderDemo.class.getClassLoader();
while(cl ! = null) {
System.out.println(cl.getClass().getName());
cl = cl.getParent();
}
System.out.println(String.class.getClassLoader());
}
}

输出为:

1
2
3
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null

ClassLoader有一个静态方法,可以获取默认的系统类加载器:

1
public static ClassLoader getSystemClassLoader()

ClassLoader中有一个主要方法,用于加载类:

1
public Class<? > loadClass(String name) throws ClassNotFoundException

比如:

1
2
3
4
5
6
7
8
ClassLoader cl = ClassLoader.getSystemClassLoader();
try {
Class<? > cls = cl.loadClass("java.util.ArrayList");
ClassLoader actualLoader = cls.getClassLoader();
System.out.println(actualLoader);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

需要说明的是,由于委派机制,Class的getClassLoader方法返回的不一定是调用load-Class的ClassLoader,比如,上面代码中,java.util.ArrayList实际由BootStrap ClassLoader加载,所以返回值就是null。

在反射一章,我们介绍过Class的两个静态方法forName:

1
2
3
public static Class<? > forName(String className)
public static Class<? > forName(String name,
boolean initialize, ClassLoader loader)

第一个方法使用系统类加载器加载,第二个方法指定ClassLoader,参数initialize表示加载后是否执行类的初始化代码(如static语句块),没有指定默认为true。

ClassLoader的loadClass方法与Class的forName方法都可以加载类,它们有什么不同呢?基本是一样的,不过,ClassLoader的loadClass不会执行类的初始化代码,看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CLInitDemo {
public static class Hello {
static {
System.out.println("hello");
}
};
public static void main(String[] args) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
String className = CLInitDemo.class.getName() + "$Hello";
try {
Class<? > cls = cl.loadClass(className);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

使用ClassLoader加载静态内部类Hello, Hello有一个static语句块,输出”hello”,运行该程序,类被加载了,但没有任何输出,即static语句块没有被执行。如果将loadClass的语句换为:

1
Class<? > cls = Class.forName(className);

则static语句块会被执行,屏幕将输出”hello”。

我们来看下ClassLoader的loadClass代码,以进一步理解其行为:

1
2
3
public Class<? > loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}

它调用了另一个loadClass方法,其主要代码为(省略了一些代码,加了注释,以便于理解):

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
protected Class<? > loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//首先,检查类是否已经被加载了
Class c = findLoadedClass(name);
if(c == null) {
//没被加载,先委派父ClassLoader或BootStrap ClassLoader去加载
try {
if(parent ! = null) {
//委派父ClassLoader, resolve参数固定为false
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//没找到,捕获异常,以便尝试自己加载
}
if(c == null) {
//自己去加载,findClass才是当前ClassLoader的真正加载方法
c = findClass(name);
}
}
if(resolve) {
//链接,执行static语句块
resolveClass(c);
}
return c;
}
}

参数resolve类似Class.forName中的参数initialize,可以看出,其默认值为false,即使通过自定义ClassLoader重写loadClass,设置resolve为true,它调用父ClassLoader的时候,传递的也是固定的false。findClass是一个protected方法,类ClassLoader的默认实现就是抛出ClassNotFoundException,子类应该重写该方法,实现自己的加载逻辑,后文我们会给出具体例子。