只重写findClass而不重写loadClass,因loadClass已内置双亲委派逻辑,重写易破坏机制;findClass负责字节码定位与defineClass定义,需确保类名匹配、抛出ClassNotFoundException而非返回null。

重写 findClass 是自定义类加载器的核心,目的是让 JVM 在双亲委派失败后,由你来决定如何获取并定义类字节码——不直接覆盖 loadClass,避免破坏双亲委派机制。
为什么只重写 findClass,不重写 loadClass?
loadClass 默认已实现双亲委派逻辑(先委托父加载器,失败再调用 findClass)。若直接重写它,容易绕过委派,引发 ClassNotFoundException 或重复定义、LinkageError 等问题。正确做法是:继承 ClassLoader,仅重写 findClass(String name),在其中完成“定位字节码 → 调用 defineClass”两步。
findClass 的标准写法(含关键细节)
以从指定目录或 Jar 包加载类为例:
- 将类全限定名(如
com.example.MyClass)转为路径格式:com/example/MyClass.class - 通过 I/O 读取该路径对应的字节数组(注意处理异常和资源关闭)
- 调用
defineClass(name, bytes, 0, bytes.length)返回Class> - 若找不到字节码,必须抛出
ClassNotFoundException(不能返回 null)
示例代码:
public class MyClassLoader extends ClassLoader {
private final File classDir;
public MyClassLoader(File classDir) {
this.classDir = classDir;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] data = loadByteCode(name);
if (data == null) {
throw new ClassNotFoundException(name);
}
return defineClass(name, data, 0, data.length);
}
private byte[] loadByteCode(String name) {
String path = classDir.getAbsolutePath() + File.separator +
name.replace('.', File.separatorChar) + ".class";
try (FileInputStream fis = new FileInputStream(path)) {
return fis.readAllBytes();
} catch (IOException e) {
return null;
}
}
}
常见注意事项
-
类名必须与字节码中声明的完全一致:
defineClass会校验,不匹配会抛ClassFormatError - 不要在
findClass中调用resolveClass(除非明确需要立即链接),JVM 通常会在首次主动使用时自动链接 - 如果需加密/解密字节码,放在
loadByteCode里处理,别动defineClass的原始输入 - 多个自定义加载器之间若共享类,注意类隔离性——相同字节码被不同加载器加载,仍是不同类
什么时候需要额外处理 resolveClass?
绝大多数场景不需要。仅当你要确保类立即完成验证、准备、初始化(比如插件系统要求类一加载就可反射调用静态方法),才在 findClass 返回前加一句:resolveClass(cls)。但要注意这会触发类的静态初始化块执行。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











