自定义 classloader 不能直接破坏双亲委派,因为 loadclass() 默认先委托父加载器,重写 findclass() 无效;必须重写 loadclass() 并跳过 super.loadclass() 调用。

为什么自定义 ClassLoader 不能直接破坏双亲委派?
因为 JDK 的 ClassLoader.loadClass() 默认实现里写了先委托父加载器,再自己找类——你重写 findClass() 没用,调用链根本不会走到那儿。真正要绕过,得重写 loadClass() 本身,且必须显式跳过 super.loadClass() 调用。
常见错误是只重写了 findClass(),结果类还是被 AppClassLoader 或 ExtClassLoader 提前加载了,导致自定义逻辑完全没生效。
- 必须重写
loadClass(String name, boolean resolve),且不调用super.loadClass() - 如果仍需部分委托(比如只隔离某些包),得手动判断
name.startsWith("com.mybiz.")再决定是否 self-load - JDK 9+ 模块系统下,
getSystemClassLoader()返回的是jdk.internal.loader.ClassLoaders$AppClassLoader,不是传统意义上的URLClassLoader,反射获取ucp字段可能失败
Tomcat 是怎么“假装”破坏双亲委派的?
它没真破坏——而是把双亲委派的“亲”换掉了。WebAppClassLoader 的父加载器不是 AppClassLoader,而是 StandardClassLoader(即 catalina-loader),而后者又把 shared-loader 和 common-loader 当作自己的 parent。整个链条仍是委派的,只是拓扑结构变了。
关键点在于:每个 Web 应用有独立的 WebAppClassLoader 实例,且它的 delegate 属性默认为 false(注意不是类加载器的 parent!),这意味着它会优先用自己的 findClass() 加载 /WEB-INF/classes 和 /WEB-INF/lib 下的类,而不是先扔给 parent。
立即学习“Java免费学习笔记(深入)”;
-
WebAppClassLoader.delegate = false是行为开关,不是继承关系开关 -
/WEB-INF/classes>/WEB-INF/lib/*.jar>shared-loader>common-loader>system classloader,这是实际查找顺序 - Servlet 规范要求 JSP、
javax.servlet.*等 API 类必须由容器提供,所以 Tomcat 把这些包名硬编码进“跳过委派”白名单,避免应用打包冲突
Thread.currentThread().setContextClassLoader() 到底在改谁?
它改的是当前线程私有的 contextClassLoader 字段,和任何类加载器的父子关系无关。这个字段纯粹是个“传参”机制——比如 DriverManager 在加载 JDBC 驱动时,会用 Thread.currentThread().getContextClassLoader() 去加载驱动类,而不是用 DriverManager.class.getClassLoader()。
所以你在 Spring Boot 启动时看到一堆 setContextClassLoader(this.getClassLoader()),本质是在告诉第三方库:“别用你自己的类加载器,用我这个应用级的”。但如果你忘了恢复、或在线程池里复用线程,就容易出现类找不到或类型转换异常(ClassCastException)。
- 线程池中务必在任务开始时显式设置,结束时恢复原值(尤其用
ForkJoinPool或CompletableFuture时) -
new Thread(() -> { ... }).start()创建的新线程,contextClassLoader默认继承自父线程,不是系统类加载器 - Spring 的
ContextRefreshedEvent回调里,contextClassLoader通常是LaunchedURLClassLoader,但异步监听器可能跑在TaskScheduler线程上,此时可能是AppClassLoader
破坏双亲委派后最常踩的三个坑
不是“能不能破”,而是破完之后 JVM 不认识你加载的类了。JDBC 驱动注册失败、Spring Bean 类型匹配报错、甚至 java.lang.String 都可能被重复加载(虽然极少发生,但一旦发生就是 NoClassDefFoundError 或 LinkageError)。
- 同一个类被两个不同
ClassLoader加载,哪怕字节码一模一样,JVM 也认为是两个类型,赋值、转型、instanceof全挂 -
static变量按类加载器隔离——你以为的单例,在多个 classloader 下其实是多份 - JNI 库路径(
System.loadLibrary())依赖ClassLoader.findLibrary(),自定义 loader 若没重写它,会找不到 so/dll
真正难的从来不是写个新 ClassLoader,而是理清哪些类必须共享、哪些必须隔离、以及谁来负责释放资源(比如 URLClassLoader 的 close() 必须显式调用,否则 jar 文件句柄泄漏)。










