让Java运行时在合适的地方加载类——自定义类加载器
2024-01-29 21:45:27
Java虚拟机中预定义的三种类加载器中,我们熟知且常用的是启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和系统类加载器(System ClassLoader)。除了这三种以外,我们还可以编写自己的类加载器,这就是自定义类加载器。很多时候,自定义类加载器是在我们对于类的位置、加载、运行时机有特殊要求的时候使用,比如在沙盒环境、插件机制、热加载等场景中使用。
我们经常会听说“双亲委派”这个词。类加载器(ClassLoader)中的双亲委派模型指的是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,依次向上委派,只有父类加载器无法完成这个请求时,即父类加载器返回 null 时,子加载器才会尝试自己去加载。这样做的目的是为了防止重复加载和保证安全性。
比如 Tomcat 是一个常见的 Web 服务器,在 Java 中,它是如何加载我们开发的 Web 应用的?我们知道 Tomcat 中有自己的类加载器,它会先从我们配置的 classpath 中加载对应的类文件,这个是系统类加载器。它将 Java 虚拟机预定义的类加载器——扩展类加载器和启动类加载器进行扩展,然后,在扩展类加载器和系统类加载器这两个类加载器对加载类文件的路径进行限制,这样 Tomcat 的类加载器就可以将我们的 Web 应用中用到的第三方 JAR 包加载进去。
还有些比如 OSGI(Open Service Gateway Initiative,开放服务网关计划),使用自定义类加载器作为插件的承载容器。OSGI 是一个动态模块化服务平台,实现了 Java 模块化,类似 Android 中的 APK。它对 Java 标准类库的 Jar 包进行改造,并通过自定义类加载器,把它当作插件装入到 Java 虚拟机,动态加载它们。我们也可以通过继承 ClassLoader 并重写它的方法来实现自己的类加载器。
那么,如何自定义自己的类加载器呢?自定义类加载器需要继承 ClassLoader,并重写它的方法,比如 findClass() 方法。
public class CustomClassLoader extends ClassLoader {
private String classpath;
public CustomClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//省略其他代码
//从自定义路径中加载类文件
byte[] classData = loadClassData(name);
//把二进制字节码转化成类对象
return defineClass(name, classData, 0, classData.length);
}
}
接下来,我们就自定义一个类加载器,从一个自己指定的目录中加载“Hello.java”文件,并执行它的 main() 方法。
- 编写 Hello.java 文件
public class Hello {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
- 将 Hello.java 文件编译成 Hello.class 文件
javac Hello.java
- 创建自定义类加载器
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class CustomClassLoader extends ClassLoader {
private String classpath;
public CustomClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//省略其他代码
//从自定义路径中加载类文件
byte[] classData = loadClassData(name);
//把二进制字节码转化成类对象
return defineClass(name, classData, 0, classData.length);
}
private byte[] loadClassData(String name) {
String path = classpath + File.separator + name.replace('.', File.separatorChar) + ".class";
try (FileInputStream fis = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int len;
while ((len = fis.read()) != -1) {
baos.write(len);
}
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
//自定义类加载器加载 Hello.class 文件
CustomClassLoader customClassLoader = new CustomClassLoader("D:\\test\\");
try {
Class<?> helloClass = customClassLoader.loadClass("Hello");
helloClass.getMethod("main", String[].class).invoke(null, (Object) new String[]{});
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 运行 CustomClassLoader
java CustomClassLoader
此时,我们就可以看到控制台输出“Hello World!”。
通过自定义类加载器,我们可以灵活地加载类,从而实现各种自定义的功能。