打破双亲委派是为了实现类隔离或逆向加载,如web应用多版本冲突或jdbc驱动由bootstrap加载器调用应用类;需重写loadclass而非仅findclass,先检查核心包交父加载,再自定义路径加载,失败后才委托父类;tomcat的webappclassloader是典型实践,分层控制,局部翻转、全局守序。

明确为什么要打破双亲委派
双亲委派是默认安全策略,不是强制铁律。真正需要打破它,往往是因为场景要求类隔离或逆向加载:比如 Web 应用中两个模块依赖不同版本的 commons-collections,按默认委派会被顶层统一加载一份,引发 NoSuchMethodError;又比如 JDBC 驱动(mysql-connector-java.jar)在应用 classpath,而 DriverManager 在 rt.jar(由 Bootstrap 加载),后者需主动加载前者——但 Bootstrap 无法反向委托给应用类加载器。这类需求决定了必须有节制地干预加载顺序。
重写 loadClass 方法是关键动作
Java 类加载对外入口是 loadClass(String name),它的默认实现就是递归委托父加载器。要打破委派,就必须覆盖这个方法,而不是只重写 findClass——后者只是“自己加载”的最后一步,不改 loadClass,请求永远先飞向上层。
典型做法是翻转查找顺序:
- 先检查类名是否属于核心包(如
java.、javax.、sun.),这些必须交由父加载器处理,保障安全 - 再检查目标类是否在自定义路径(如
/WEB-INF/classes或某个指定目录),若存在则调用findClass(name)自行加载 - 仅当
findClass抛出ClassNotFoundException时,才调用super.loadClass(name)委托父加载器
配合 findClass 完成实际加载
findClass 不参与委派逻辑,只负责定位字节码并交给 defineClass 转为 Class 对象。你需要:
- 根据类全限定名拼出对应路径(如
com.example.Service→com/example/Service.class) - 从文件系统、JAR 包或网络读取字节流,得到
byte[] - 调用
defineClass(name, bytes, 0, bytes.length)完成解析和注册
注意:defineClass 是受保护方法,只能在继承自 ClassLoader 的子类中调用,且不可被重写。
Tomcat 是最成熟的实践参考
Tomcat 的 WebAppClassLoader 就是标准范本:它把 Web 应用内的类优先从 /WEB-INF/classes 和 /WEB-INF/lib 加载,同时严格保护 java.* 等包走父加载器。它的设计不是全盘否定委派,而是分层控制——CommonClassLoader 和 SharedClassLoader 仍严格遵循委派,确保 servlet-api.jar 全局唯一;只有 Web 层做了有边界的翻转。
这种“局部破坏+全局守序”的思路,比简单重写 loadClass 更稳妥,也更贴近真实工程需求。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











