Tomcat 类加载机制
JVM 类加载器层级
JVM 有三层内置类加载器,自上而下:
Bootstrap ClassLoader ← 加载 JDK 核心类 (rt.jar)
└── Extension ClassLoader ← 加载 JDK 扩展类
└── Application ClassLoader ← 加载应用 classpath
加载规则是双亲委派:子加载器收到请求先交给父加载器,父加载器找不到才自己加载。目的是防止用户代码替换掉 JDK 核心类。
Tomcat 打破了双亲委派
Tomcat 为了支持多个 WebApp 互相隔离,自己实现了一套类加载器:
Bootstrap ClassLoader
└── Extension ClassLoader
└── Application ClassLoader (Tomcat自身)
└── WebApp ClassLoader ← 每个应用独立一个
WebApp ClassLoader 故意反转了加载顺序,优先自己加载,而不是先交给父加载器。这样不同 WebApp 的同名类互不干扰。
WebApp ClassLoader 内部加载顺序
在单个 WebApp 内部,顺序是固定的:
1. /WEB-INF/classes/ ← 优先
2. /WEB-INF/lib/*.jar ← 其次
3. 父ClassLoader(Tomcat公共类)
classes/ 永远比 lib/ 先搜索,所以同名类 classes 里的赢。
为什么这样设计
本质是 classpath 的线性搜索------JVM 找类就像找文件,按路径顺序找到第一个匹配的就用,同名类只加载一次,后面的直接忽略。
这个特性被广泛利用:
| 场景 | 做法 |
|---|---|
| 热补丁 | 把修复后的 class 放到 classpath 靠前的位置 |
| 框架扩展 | 在 classes 里放同名类覆盖 jar 里的默认实现 |
| 单元测试 | 用 Mock 类替换真实实现 |
一句话总结
Tomcat 的 WebApp ClassLoader 主动打破双亲委派,让
classes/优先于lib/,同名类只认第一个找到的,后面的直接丢弃。