只重写 findClass 而不重写 loadClass,是因为 loadClass 已内置双亲委派逻辑,重写它易破坏委派机制,导致核心类加载异常;findClass 专用于自定义字节码获取与 defineClass。

在 Java 类加载机制中,自定义类加载器重写 findClass 方法,是为了控制字节码的获取方式,而不是直接覆盖 loadClass——因为后者已内置双亲委派逻辑。正确做法是继承 ClassLoader,重写 findClass(String name),在其中完成类名到字节码的解析与定义。
为什么只重写 findClass,不重写 loadClass?
ClassLoader.loadClass() 默认实现已包含双亲委派流程:先委托父加载器尝试加载,失败后才调用 findClass。若直接重写 loadClass,容易破坏委派机制,导致 java.lang.* 等核心类加载异常或安全问题。因此,JDK 明确建议:自定义加载逻辑应放在 findClass 中。
findClass 的基本实现步骤
重写 findClass 时需完成三件事:定位字节码(如从文件、网络、加密流)、读取为 byte[]、调用 defineClass 转为 Class 对象。示例:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
// 示例:从指定目录加载 .class 文件
立即学习“Java免费学习笔记(深入)”;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path = classNameToPath(name); // 如 com.example.Foo → /path/com/example/Foo.class
byte[] bytes = readBytesFromFile(path); // 自行实现读取逻辑
if (bytes == null) {
throw new ClassNotFoundException(name);
}
return defineClass(name, bytes, 0, bytes.length);
}
注意事项和常见陷阱
-
不要在 findClass 中调用 defineClass 以外的类加载方法(如
loadClass),否则可能引发循环委托或重复加载 -
类名必须使用全限定名(含包路径),且与字节码中的
ConstantPool一致,否则defineClass会抛NoClassDefFoundError -
若字节码经过加密或动态生成,需确保验证通过;必要时可重写
resolveClass或调用defineClass的带ProtectionDomain版本 -
资源路径需注意类名转路径的转换规则:将
.替换为/,并补上.class后缀,同时处理 URL 编码或空格等特殊字符
什么时候需要绕过双亲委派?
典型场景包括:热部署(如 Tomcat 每个 WebApp 使用独立加载器)、OSGi 模块隔离、加密类加载、运行时动态生成类(如 Javassist、CGLIB)。此时仍应保留 findClass 的职责,仅在必要时在 loadClass 中修改委派顺序(例如先自己找,再委托父类),但须格外谨慎。

















