24.4 自定义ClassLoader

24.4 自定义ClassLoader

Java类加载机制的强大之处在于,我们可以创建自定义的ClassLoader,自定义Class-Loader是Tomcat实现应用隔离、支持JSP、OSGI实现动态模块化的基础。

怎么自定义呢?一般而言,继承类ClassLoader,重写findClass就可以了。怎么实现findClass呢?使用自己的逻辑寻找class文件字节码的字节形式,找到后,使用如下方法转换为Class对象:

1
protected final Class<? > defineClass(String name, byte[] b, int off, int len)

name表示类名,b是存放字节码数据的字节数组,有效数据从off开始,长度为len。看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyClassLoader extends ClassLoader {
private static final String BASE_DIR = "data/c87/";
@Override
protected Class<? > findClass(String name) throws ClassNotFoundException {
String fileName = name.replaceAll("\\.", "/");
fileName = BASE_DIR + fileName + ".class";
try {
byte[] bytes = BinaryFileUtils.readFileToByteArray(fileName);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException ex) {
throw new ClassNotFoundException("failed to load class " + name, ex);
}
}
}

MyClassLoader从BASE_DIR下的路径中加载类,它使用了我们在第13章介绍的readFileToByteArray方法读取文件,转换为byte数组。MyClassLoader没有指定父Class-Loader,默认是系统类加载器,即ClassLoader.getSystemClassLoader()的返回值,不过,Class-Loader有一个可重写的构造方法,可以指定父ClassLoader:

1
protected ClassLoader(ClassLoader parent)

MyClassLoader有什么用呢?将BASE_DIR加到classpath中不就行了,确实可以,这里主要是演示基本用法,实际中,可以从Web服务器、数据库或缓存服务器获取bytes数组,这就不是系统类加载器能做到的了。

不过,不把BASE_DIR放到classpath中,而是使用MyClassLoader加载,还有一个很大的好处,那就是可以创建多个MyClassLoader,对同一个类,每个MyClassLoader都可以加载一次,得到同一个类的不同Class对象,比如:

1
2
3
4
5
6
7
8
MyClassLoader cl1 = new MyClassLoader();
String className = "shuo.laoma.dynamic.c87.HelloService";
Class<? > class1 = cl1.loadClass(className);
MyClassLoader cl2 = new MyClassLoader();
Class<? > class2 = cl2.loadClass(className);
if(class1 ! = class2) {
System.out.println("different classes");
}

cl1和cl2是两个不同的ClassLoader, class1和class2对应的类名一样,但它们是不同的对象。

但,这到底有什么用呢?

1)可以实现隔离。一个复杂的程序,内部可能按模块组织,不同模块可能使用同一个类,但使用的是不同版本,如果使用同一个类加载器,它们是无法共存的,不同模块使用不同的类加载器就可以实现隔离,Tomcat使用它隔离不同的Web应用,OSGI使用它隔离不同模块。
2)可以实现热部署。使用同一个ClassLoader,类只会被加载一次,加载后,即使class文件已经变了,再次加载,得到的也还是原来的Class对象,而使用MyClassLoader,则可以先创建一个新的ClassLoader,再用它加载Class,得到的Class对象就是新的,从而实现动态更新。

下面,我们来具体看热部署的示例。