核心是重写loadclass方法跳过super.loadclass调用,自主判断包名(如com.example.hotswap.*)后直接findclass→defineclass加载,其余类可抛classnotfoundexception,从而彻底绕过双亲委派。

要让自定义类加载器打破双亲委派机制,核心是在重写 loadClass 方法时,**不调用父类的 loadClass(即跳过双亲委派逻辑)**,而是直接用自己的逻辑去加载类(比如从指定路径、网络、加密字节码等位置加载),并在必要时调用 defineClass 完成类的定义。
重写 loadClass 方法,绕过父类委派
默认的 ClassLoader.loadClass 实现会先委托给父加载器,找不到才自己加载。要打破它,就不能沿用默认流程:
- 不要调用
super.loadClass(name)或getParent().loadClass(name) - 直接在自己的方法里判断是否该由本加载器加载(例如根据包名、前缀、配置规则)
- 若匹配,则调用
findClass(name)→ 自定义字节码获取逻辑 →defineClass - 若不匹配,可选择抛异常、返回 null,或按需委托(此时是“有选择地委派”,而非强制双亲委派)
典型实现示例(仅加载特定包下的类)
比如只加载 com.example.hotswap.* 下的类,其余一律不处理:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
public class HotSwapClassLoader extends ClassLoader {
private final String classPath;
public HotSwapClassLoader(String classPath, ClassLoader parent) {
super(parent); // 仍可持有 parent,但不强制委派
this.classPath = classPath;
}
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 【关键】不走 super.loadClass,完全自主控制
if (name.startsWith("com.example.hotswap.")) {
return findClass(name);
}
// 其他类:可以选择抛异常、返回 null,或显式委派给 parent(非必须)
throw new ClassNotFoundException(name + " not supported by this loader");
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadClassBytes(name); // 自定义读取字节码(如从文件、jar、内存等)
if (bytes == null) {
throw new ClassNotFoundException(name);
}
return defineClass(name, bytes, 0, bytes.length);
}
private byte[] loadClassBytes(String name) {
String path = classPath + "/" + name.replace('.', '/') + ".class";
try (InputStream is = new FileInputStream(path)) {
return is.readAllBytes();
} catch (IOException e) {
return null;
}
}
}
注意 defineClass 的安全与可见性
defineClass 是受保护方法,子类可调用,但要注意:
- 它不会自动链接(linking),如需立即验证、准备、初始化,需配合
resolveClass - 若类已由其他加载器定义过(相同全限定名 + 不同加载器),JVM 会拒绝重复定义,抛
LinkageError - 确保字节码格式合法,否则抛
ClassFormatError - 如果涉及模块系统(Java 9+),还需考虑
ModuleLayer和defineClass的模块归属问题
常见用途和风险提醒
打破双亲委派常用于:
- 热部署/热替换(如 Spring Boot DevTools、OSGi、JRebel)
- 隔离插件或沙箱环境(不同插件用不同加载器,避免类冲突)
- 动态加载加密或远程字节码(需解密后再 defineClass)
但需警惕:
- 破坏了 JVM 类型一致性基础,容易引发
NoClassDefFoundError、ClassCastException(同一类被多个加载器加载,视为不同类型) - 可能绕过安全管理器或模块约束,带来安全隐患
- 线程上下文类加载器(TCCL)被误设时,可能导致框架(如 JNDI、JAX-WS)行为异常
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










