应重写 findclass 而不是 loadclass,因为 loadclass 已内置双亲委派逻辑,重写易破坏类隔离;findclass 职责单一,只负责读取字节码并调用 defineclass,是安全的扩展点。

在 Java 类加载机制中,自定义类加载器时,应重写 findClass 方法,而不是直接重写 loadClass。这是为了遵循双亲委派模型,同时保证类加载逻辑的安全性和可维护性。
为什么重写 findClass 而不是 loadClass
loadClass 是类加载的入口方法,JDK 默认实现已内置双亲委派逻辑:先委托父加载器尝试加载,失败后才调用 findClass。如果直接重写 loadClass,容易绕过双亲委派,破坏类隔离、引发 ClassNotFoundException 或 LinkageError(如相同类被不同加载器重复加载)。
findClass 的职责单一:只负责「从特定来源(如文件、网络、数据库)读取字节码并调用 defineClass 转为 Class 对象」。它天然位于双亲委派链条末端,是安全、推荐的扩展点。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
正确重写 findClass 的步骤
- 继承
ClassLoader(不要继承URLClassLoader等具体子类,除非明确需要其功能) - 重写
protected Class> findClass(String name) - 在方法内:将类名转为资源路径(如
name.replace('.', '/') + ".class") - 通过自定义方式获取字节码(如读取本地文件、下载远程字节流、解密内存数据等)
- 调用
defineClass(name, byte[], offset, length)返回 Class 对象 - 若字节码不存在或解析失败,抛出
ClassNotFoundException
loadClass 通常无需重写,但有例外
绝大多数场景下,保持默认 loadClass 行为即可。只有在需要**打破双亲委派**时才考虑重写(如 OSGi、热部署框架、插件系统),此时需谨慎处理:
- 先检查是否为本加载器应负责的类(例如按包名前缀过滤)
- 命中则调用
findClass;否则显式委托父加载器(super.loadClass(name, resolve)) - 避免无条件跳过父加载器,否则可能加载不到
java.*或核心类
一个 minimal 示例
以下是一个从指定目录加载 class 文件的简单实现:
public class MyClassLoader extends ClassLoader {
private final File classDir;
public MyClassLoader(File classDir, ClassLoader parent) {
super(parent);
this.classDir = classDir;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadClassBytes(name);
if (bytes == null) {
throw new ClassNotFoundException(name);
}
return defineClass(name, bytes, 0, bytes.length);
}
private byte[] loadClassBytes(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;
}
}
}
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










