两个同名类引发线上 ClassCastException:双亲委派被打破的三个地方,我踩过其中一个
title: 两个同名类引发线上 ClassCastException双亲委派被打破的三个地方我踩过其中一个date: 2026-10-01tags: [JVM, 类加载器, 双亲委派, Tomcat, SPI, 源码解析, Java]2024 年我们做老系统容器化把一个 WAR 包往内嵌 Tomcat 迁移。迁移后接口一调就抛ClassCastException: com.example.model.User cannot be cast to com.example.model.User——同一个全限定名 cast 自己都报错。整个团队盯着这行日志沉默了半分钟最后是我在对象前面打印getClass().getClassLoader()才真相大白两个User类一个是AppClassLoader加载的一个是Fat Jar 里LaunchedURLClassLoader加载的JVM 眼里这是两个不同的类cast 当然失败。这个事故让我把类加载器和双亲委派的源码认真啃了一遍。这篇文章把委派模型怎么工作、哪些地方在刻意打破它、以及打破之后要付出什么代价讲清楚。一、先看源码ClassLoader.loadClass 的三段式逻辑双亲委派的核心就在ClassLoader#loadClass去掉无关细节后主干是三段protected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 第 1 段检查自己以及缓存是否已经加载过 Class? c findLoadedClass(name); if (c null) { try { // 第 2 段有父加载器就先委派给父亲 if (parent ! null) { c parent.loadClass(name, false); } else { // 没有父加载器交给启动类加载器Bootstrap c findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 父加载器找不到什么都不做往下走 } if (c null) { // 第 3 段父亲加载不了才自己动手 findClass c findClass(name); } } return c; } }逐行解释- 第 1 段保证类的幂等性同一个加载器加载过的类直接命中缓存不会重复加载。- 第 2 段是委派儿子先问父亲父亲再问爷爷一路问到 Bootstrap。父亲能加载的类儿子永远没有机会重复加载。- 第 3 段是回传父亲一路抛 ClassNotFoundException才轮到最初的请求者自己findClass。这个模型带来的核心保证是核心类库的唯一性。你写一个java.lang.String放进自己的 jar运行时用的仍然是 Bootstrap 加载的 JDK String因为你的 AppClassLoader 一开口就把请求委派上去了。没有这个机制伪造核心类的攻击和核心类重复加载的混乱会同时发生。二、事故复盘同名类的 ClassCastException 是怎么来的回到开头的事故。Spring Boot 的 Fat Jar 用LaunchedURLClassLoader加载BOOT-INF/classes和BOOT-INF/lib里的类它的父加载器是 AppClassLoader。当时我们的工程结构里有个尴尬的历史包袱一部分公共类被打进了 Fat Jar 的根目录旧打包脚本残留另一份相同代码在BOOT-INF/classes里。两个不同 URL 搜索路径上的同名类被两个不同的类加载器各自加载了一次。JVM 中判定两个类是否相同用的是全限定名 类加载器二元组所以它们是完全独立的两个类型赋值和 cast 都会失败。排查动作其实只有两行System.out.println(obj.getClass().getClassLoader()); System.out.println(User.class.getClassLoader()); // 输出 // org.springframework.boot.loader.LaunchedURLClassLoader1a2b3c // org.springframework.boot.loader.LaunchedURLClassLoader4d5e6f - 实例不同两个类加载器实例不同搜索路径不同但打印的类名一样基本就可以定性。修复是清理打包脚本保证同名类在全工程只有一份物理来源。这个教训我写进了团队的发布检查单升级打包方式后必须扫一遍产物里有没有重复的全限定类名。三、双亲委派被打破的三个地方委派模型不是铁板一块JDK 生态里有三个著名的破坏者各自动机不同。第一个SPI 与线程上下文类加载器。JDBC 是经典案例。java.sql.DriverManager由 Bootstrap 加载但它要加载的厂商驱动比如 mysql-connector 的com.mysql.cj.jdbc.Driver在应用 classpath 上Bootstrap 根本看不见。如果死守委派模型DriverManager 永远加载不到驱动。解法是在ServiceLoader.load里取当前线程的上下文类加载器让父加载器的代码反向使用子加载器加载类// ServiceLoader.load 的实现简化 public static S ServiceLoaderS load(ClassS service) { // 取线程上下文类加载器通常是 AppClassLoader ClassLoader cl Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }这一行就是父委派反向给子的桥。代价是委派模型的唯一性保证在这个通道上失效了这也是为什么同一个驱动类偶尔会被加载两次的根源。第二个Tomcat 的 WebappClassLoader。一个 Tomcat 部署多个应用每个 Webapp 一个类加载器。它没有完全遵循先委派策略是先自己找找不到再委派给父加载器。动机是应用隔离两个应用各自依赖不同版本的同一个 jar 互不干扰。这也正是我在事故里看到双类加载器的底层原因。第三个OSGi / 模块化的网状委派。委派关系不再是一棵树而是按包名和模块导入导出关系构成的图。动机是支持模块热替换。复杂度也相应爆炸这也是我对网状类加载方案一直持保留态度的原因收益主要给了中间件厂商复杂度却要业务开发者消化。四、自定义类加载器findClass 的最小骨架虽然我不建议业务代码乱写类加载器但掌握findClass的最小骨架对理解整个体系非常有用——你看懂了这个骨架Tomcat 和 OSGi 的委派变体都只是在它之上改写第 2 段和第 3 段的顺序public class DirClassLoader extends ClassLoader { private final String baseDir; public DirClassLoader(String baseDir, ClassLoader parent) { super(parent); // 显式指定父加载器委派链的起点 this.baseDir baseDir; } Override protected Class? findClass(String name) throws ClassNotFoundException { // 全限定名转文件路径 String path baseDir name.replace(., /) .class; byte[] bytes; try (InputStream in new FileInputStream(path); ByteArrayOutputStream bos new ByteArrayOutputStream()) { byte[] buf new byte[4096]; int len; while ((len in.read(buf)) 0) { bos.write(buf, 0, len); } bytes bos.toByteArray(); } catch (IOException e) { // 父加载器和本地目录都没有向调用方明确报错 throw new ClassNotFoundException(name, e); } // defineClass 完成字节码到 Class 对象的转换并自动登记进缓存 return defineClass(name, bytes, 0, bytes.length); } }逐行解释- 构造器里传 parent决定这个加载器挂在委派链的哪个位置。-findClass只负责一件事从自己的搜索路径找到字节码。委派逻辑不要在这里写loadClass已经处理好了。-defineClass是 JVM 的桥把字节数组转成方法区的 Class 对象同时把加载器 类名登记进内部缓存下次findLoadedClass就能命中。- 找不到必须抛 ClassNotFoundException 而不是返回 null否则委派链的报错语义会被破坏。测试代码里一个值得玩味的细节ClassLoader loader1 new DirClassLoader(/data/app/, ClassLoader.getSystemClassLoader()); Class? c1 loader1.loadClass(com.example.model.User); ClassLoader loader2 new DirClassLoader(/data/app/, ClassLoader.getSystemClassLoader()); Class? c2 loader2.loadClass(com.example.model.User); System.out.println(c1 c2); // false System.out.println(c1.isInstance(c2.newInstance())); // false两个独立的 DirClassLoader 实例加载同一个 class 文件得到两个互相不兼容的 Class。这就是开头 ClassCastException 的最小复现——同一个文件两个加载器两个世界。五、Tomcat 的委派变体先自己找找不到再委派WebappClassLoader对标准委派做了一处关键改写简化后的 loadClass// WebappClassLoader.loadClass简化示意 public Class? loadClass(String name, boolean resolve) throws ClassNotFoundException { // 1. 检查本地缓存本 webapp 已加载的类 Class? clazz findLoadedClass0(name); if (clazz null) { // 2. 先在自己的仓库里找WEB-INF/classes 与 WEB-INF/lib clazz findClass(name); if (clazz null) { // 3. 自己没有才委派给父加载器 clazz super.loadClass(name, false); } } return clazz; }这个顺序调换的效果是应用自带的依赖优先于容器公共目录的同名类两个 webapp 各带各的 Spring 版本互不干扰。但也不是无脑先自己找——java.开头的类和被Loader delegatetrue标记的包仍会强制先委派否则有人放一个恶意的java.lang.String进 WEB-INF 就能劫持核心类。Tomcat 的取舍方案很值得读隔离性和安全性之间画了一条清晰的线而不是简单二选一。六、我的取舍判断业务代码永远不要手写自定义 ClassLoader 去加载 classpath 上的普通类能不碰就不碰——唯一性保证一旦破坏问题会在运行期以最诡异的形式出现。遇到cannot be cast to 同名类第一反应打印两边的 classLoader五分钟定性比翻依赖树快得多。需要隔离多版本依赖时优先考虑拆服务或 shaded jar重命名包而不是引入自研类加载器框架。排查依赖冲突用mvn dependency:tree配合 Arthas 的sc -d看类实际加载来源眼见为实。七、复盘真实数字事故场景WAR 迁内嵌 Tomcat接口 100% 报 ClassCastException定位耗时打印 classLoader 后 5 分钟定性清理打包脚本 1 小时根因产物Fat Jar 根目录与 BOOT-INF/classes 存在同名类双份物理来源沉淀发布检查单新增产物重复类扫描此后一年零复发八、思考题在你的环境里跑一下Thread.currentThread().getContextClassLoader()打印看看它是什么加载器。再想想如果有人在子线程里 new 了线程池并把自定义类加载器忘了传递会发生什么欢迎在评论区写下你的推测。

相关新闻

电话里让改就改了?结算时这笔钱没人认

电话里让改就改了?结算时这笔钱没人认

一个做市政管网的项目,施工到一半,甲方现场代表打来电话:这段管径改大一号,先做着,手续后面补。项目经理不敢耽搁,当天就调了料、改了做法。半年后结算,这笔材料加人工多花了十几万,…

2026/10/1 15:32:19 阅读更多 →
从零到第一张AI图:Qwen-Image-2.1-viggle-turbo diffusers快速上手完整教程

从零到第一张AI图:Qwen-Image-2.1-viggle-turbo diffusers快速上手完整教程

从零到第一张AI图:Qwen-Image-2.1-viggle-turbo diffusers快速上手完整教程 【免费下载链接】Qwen-Image-2.1-viggle-turbo 项目地址: https://ai.gitcode.com/hf_mirrors/Viggle/Qwen-Image-2.1-viggle-turbo Qwen-Image-2.1-viggle-turbo 是由 Viggle 基于…

2026/10/1 15:32:19 阅读更多 →
书匠策AI的AI PPT功能:你论文写得再好,不会“讲”也是白搭

书匠策AI的AI PPT功能:你论文写得再好,不会“讲”也是白搭

官网:www.shujiangce.com | 微信 公众号 :书匠策AI 公众号搜一搜 书匠策AI,官网 www.shujiangce.com。今天不聊怎么把论文“写”出来,聊一个更被低估的能力:怎么把论文“讲”出来。 论文写完那一刻,你…

2026/10/1 15:32:19 阅读更多 →

最新新闻

独家拆解NVIDIA最新AI Agent实践:一个ROS 2节点如何完成零拷贝迁移

独家拆解NVIDIA最新AI Agent实践:一个ROS 2节点如何完成零拷贝迁移

摘要:NVIDIA用AI Coding Agent和专用Skill迁移ROS 2节点,让GPU数据尽量绕开CPU拷贝。真正值得测试团队学习的不是“AI改了代码”,而是如何用语义、传输路径、回退行为和性能证据证明这次迁移可靠。 一个ROS 2视觉节点已经在GPU上跑TensorRT&a…

2026/10/1 16:18:38 阅读更多 →
Designer Skills视觉评审实战:/critique-screen如何一键输出优先级修复清单,快速定位设计缺陷

Designer Skills视觉评审实战:/critique-screen如何一键输出优先级修复清单,快速定位设计缺陷

Designer Skills视觉评审实战:/critique-screen如何一键输出优先级修复清单,快速定位设计缺陷 【免费下载链接】designer-skills Designer Skills Collection: agentic skills, commands, and plugins for design — from research to systems, UI, inte…

2026/10/1 16:18:38 阅读更多 →
逻辑严谨吗?8款AI写作辅助网站势力榜,毕业护航!

逻辑严谨吗?8款AI写作辅助网站势力榜,毕业护航!

论文选题总找不到方向?文献综述翻来覆去写不出新意?格式排版反复修改仍不达标? 别担心!AI论文写作工具正在重新定义学术创作方式。本文将从内容逻辑性、资料整合力、格式规范性、查重通过率四个关键维度,深度测评8款热…

2026/10/1 16:18:38 阅读更多 →
从“龙虾”到失控:自主AI智能体安全性博弈——用TaoToken统一Key复现权限边界测试

从“龙虾”到失控:自主AI智能体安全性博弈——用TaoToken统一Key复现权限边界测试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 16:18:38 阅读更多 →
数据资产价值评估全解析:从成本核算到ROI量化的实操框架

数据资产价值评估全解析:从成本核算到ROI量化的实操框架

“搞了这么多年大数据,到底给公司带回来多少真金白银?”——我敢说,十个数据负责人里,有八个被老板问到这句话时心里都会发虚。不是大家没干活,而是多数数据团队压根没建立起一套“数据资产价值评估”的机制&#xff1…

2026/10/1 16:18:38 阅读更多 →
Ever Gauzy Onboarding UI 插件实战指南:从零构建新用户引导流程

Ever Gauzy Onboarding UI 插件实战指南:从零构建新用户引导流程

后端前端企业应用MCP 服务 【免费下载链接】ever-gauzy Ever Gauzy™ - Open Business Management Platform (ERP/CRM/HRM/ATS/PM) - https://gauzy.co 项目地址: https://gitcode.com/GitHub_Trending/ev/ever-gauzy 点击查看 免费下载 导读 本文聚焦 Ever Gauzy…

2026/10/1 16:17:38 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →