ClassLoader 常用方法
ClassLoader API 速查
java.lang.ClassLoader 是所有类加载器的父类。理解它的 API,是熟练使用类加载器的基础。
核心方法一览
| 方法 | 说明 |
|---|---|
loadClass(String) | 加载指定的类,包含双亲委派逻辑 |
findClass(String) | 在自定义位置查找类,需要重写 |
defineClass(byte[], ...) | 将字节数组转换为 Class 对象 |
findLoadedClass(String) | 查询某个类是否已被加载 |
getParent() | 获取父类加载器 |
getSystemClassLoader() | 获取系统类加载器 |
getResource(String) | 查找资源文件 |
getResources(String) | 查找所有同名资源 |
loadClass:类加载的入口
loadClass() 是 ClassLoader 的核心方法,定义了双亲委派逻辑。
java
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 第一步:检查是否已加载(findLoadedClass)
Class<?> result = findLoadedClass(name);
if (result != null) {
return result;
}
// 第二步:尝试让父加载器加载
try {
ClassLoader parent = getParent();
if (parent != null) {
result = parent.loadClass(name, false);
} else {
result = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加载器找不到,不报错
}
// 第三步:自己加载
if (result == null) {
result = findClass(name);
}
// 第四步:是否解析
if (resolve) {
resolveClass(result);
}
return result;
}不要重写
loadClass(),除非你有特殊原因需要打破双亲委派。正确的做法是重写findClass()。
findClass:子类实现的核心
findClass() 是子类应该重写的方法,负责在特定位置查找字节码。
java
public class DiskClassLoader extends ClassLoader {
private String basePath;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = name.replace('.', '/') + ".class";
byte[] data = loadFromDisk(fileName);
if (data == null) {
throw new ClassNotFoundException(name);
}
return defineClass(name, data, 0, data.length);
}
private byte[] loadFromDisk(String fileName) {
// 从磁盘读取 .class 文件
// ...
return null;
}
}defineClass:将字节数组变成 Class 对象
defineClass() 是把字节数组转换成 JVM 认识的 Class 对象的关键方法。
java
// 基础用法
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
// 带 protection domain(安全域)
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain) throws ClassFormatError常见错误
java
public class DefineClassMistakes {
public static void main(String[] args) {
// 错误:defineClass 是 final 方法,不能被子类重写
// 正确做法是通过重写 findClass 间接调用
// 错误:在子类中直接调用 defineClass 而不重写 findClass
// 因为 loadClass 会先调用 findClass
// 正确模式:
// 1. 重写 findClass(),读取字节数组
// 2. 调用 defineClass() 转换成 Class 对象
}
}加密/解密场景
java
public class SecureLoader extends ClassLoader {
private byte[] decrypt(byte[] data, byte key) {
byte[] result = new byte[data.length];
for (int i = 0; i < data.length; i++) {
result[i] = (byte) (data[i] ^ key);
}
return result;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] encrypted = loadEncryptedData(name);
byte[] decrypted = decrypt(encrypted, SECRET_KEY);
return defineClass(name, decrypted, 0, decrypted.length);
}
}findLoadedClass:检查类是否已加载
java
public class CheckLoaded {
public static void main(String[] args) {
ClassLoader loader = CheckLoaded.class.getClassLoader();
// 检查类是否已被当前加载器加载
Class<?> clazz = loader.findLoadedClass("java.lang.String");
System.out.println("String loaded? " + (clazz != null));
// String 由 Bootstrap 加载,这里返回 null
// 在加载新类之前先检查
Class<?> existing = loader.findLoadedClass("com.example.MyClass");
if (existing == null) {
loader.loadClass("com.example.MyClass");
}
}
}getParent:获取父加载器
java
public class GetParentDemo {
public static void main(String[] args) {
ClassLoader app = GetParentDemo.class.getClassLoader();
ClassLoader ext = app.getParent();
ClassLoader boot = ext.getParent();
System.out.println("App: " + app); // sun.misc.Launcher$AppClassLoader
System.out.println("Ext: " + ext); // sun.misc.Launcher$ExtClassLoader
System.out.println("Boot: " + boot); // null
}
}getSystemClassLoader:获取系统类加载器
java
public class SystemLoader {
public static void main(String[] args) {
// 获取系统类加载器(就是 AppClassLoader)
ClassLoader system = ClassLoader.getSystemClassLoader();
System.out.println(system);
// 系统类加载器的 parent 是扩展类加载器
System.out.println(system.getParent());
}
}getResource / getResources:加载资源文件
ClassLoader 不仅能加载类,还能加载资源文件(如配置、图标):
java
public class ResourceDemo {
public static void main(String[] args) {
ClassLoader loader = ResourceDemo.class.getClassLoader();
// 查找单个资源
URL url = loader.getResource("application.properties");
if (url != null) {
System.out.println("Found: " + url);
}
// 查找所有同名资源
try {
Enumeration<URL> urls = loader.getResources("META-INF/MANIFEST.MF");
while (urls.hasMoreElements()) {
System.out.println("Found: " + urls.nextElement());
}
} catch (IOException e) {
e.printStackTrace();
}
// 也可以用 Class 的便捷方法(使用 Class 自己的类加载器)
InputStream is = ResourceDemo.class.getResourceAsStream("/config.xml");
// 注意:Class 的 getResourceAsStream 中 "/" 代表 classpath 根目录
}
}resolveClass:触发类的解析
java
protected void resolveClass(Class<?> c)调用 loadClass(name, true) 时,第二个参数 resolve=true 会自动调用 resolveClass。它执行类加载的最后一步——解析,把符号引用变成直接引用。
java
public class ResolveDemo {
public static void main(String[] args) throws Exception {
ClassLoader loader = ResolveDemo.class.getClassLoader();
// 不解析的加载(false)
Class<?> c1 = loader.loadClass("com.example.Foo", false);
// 解析的加载(true)
Class<?> c2 = loader.loadClass("com.example.Bar", true);
}
}通常不需要手动调用,因为 loadClass(true) 会自动处理。
完整使用示例
java
public class MyClassLoader extends ClassLoader {
private final String baseDir;
public MyClassLoader(String baseDir, ClassLoader parent) {
super(parent); // 指定父类加载器
this.baseDir = baseDir;
}
// 重写 findClass,不重写 loadClass(保留双亲委派)
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 1. 构造文件路径
String fileName = name.replace('.', File.separatorChar) + ".class";
File file = new File(baseDir, fileName);
if (!file.exists()) {
throw new ClassNotFoundException(name);
}
// 2. 读取字节数组
byte[] classData;
try (FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
classData = baos.toByteArray();
}
// 3. 调用 defineClass
return defineClass(name, classData, 0, classData.length);
}
public static void main(String[] args) throws Exception {
MyClassLoader loader = new MyClassLoader("/path/to/classes",
MyClassLoader.class.getClassLoader().getParent());
Class<?> clazz = loader.loadClass("com.example.TestClass");
System.out.println("Class: " + clazz.getName());
System.out.println("Loader: " + clazz.getClassLoader());
Object instance = clazz.getDeclaredConstructor().newInstance();
System.out.println("Instance: " + instance);
}
}本节小结
ClassLoader 的 API 设计遵循了模板方法模式:
loadClass()定义了加载的流程(双亲委派)findClass()是子类实现细节(在哪里找)defineClass()是最终转换步骤(字节数组 → Class 对象)
理解了这个设计,就能正确地使用和扩展 ClassLoader。
下一节,我们来看 双亲委派机制(原理/优势/沙箱安全),深入理解双亲委派的底层原理。
