Tomcat类隔离机制是有目的调整双亲委派顺序,而非彻底破坏:WebAppClassLoader优先加载/WEB-INF/classes与lib,再委托父加载器,实现应用间类隔离与共享库统一。

Tomcat 的类隔离机制,核心就是对双亲委派模型的「有目的破坏」——不是为了绕过规则,而是为了让规则适配真实 Web 容器场景。
双亲委派模型的本意和短板
标准双亲委派要求:子加载器收到类加载请求,先交由父加载器尝试;只有父加载器找不到,才自己加载。这保障了 java.*、javax.* 等核心类的唯一性和安全性,也避免重复加载。
但它在 Web 容器里会出问题:
- 两个 Web 应用都用了 Spring,但版本不同(比如一个用 5.3,一个用 6.1)→ 双亲委派会让顶层类加载器统一加载一份,导致运行时报
NoSuchMethodError - Tomcat 自身依赖 commons-logging 1.2,而某个应用打包了 1.1 → 按双亲委派,容器的版本先被加载,应用被迫降级
- 所有应用都要用
javax.servlet.http.HttpServlet,但又不能各自加载一份(否则跨应用传参会因 Class 对象不一致而抛ClassCastException)→ 这类规范类必须全局唯一,但普通业务类必须彼此隔离
Tomcat 的四层类加载器结构
它没抛弃双亲委派,而是重构了「谁是父、谁管什么」:
-
CommonClassLoader:加载
$CATALINA_HOME/lib下的 jar(如 servlet-api.jar、tomcat-util.jar)。它是 CatalinaClassLoader 和 SharedClassLoader 的父加载器,也是所有 WebAppClassLoader 的「祖父」。这类库被容器和所有应用共享 -
CatalinaClassLoader:加载 Tomcat 自身核心类(
$CATALINA_HOME/lib中非公共部分),对 Web 应用不可见 -
SharedClassLoader:加载
$CATALINA_BASE/lib下的 jar,供所有 Web 应用共享,但对 Tomcat 容器不可见(比如多个应用共用同一份 fastjson) -
WebAppClassLoader:每个 WAR 包独享一个。它主动打破双亲委派:收到请求后,先自己查
/WEB-INF/classes和/WEB-INF/lib,找不到才委托给 SharedClassLoader → 这样就能优先加载本应用的 spring-core-6.1.jar,而不是被上级加载器抢走
关键破坏点:WebAppClassLoader 的 loadClass 行为
它的 loadClass(String name) 方法重写了逻辑:
- 不直接调用
super.loadClass()委托父类 - 先检查是否以
java.、javax.、sun.开头 → 这些强制走父加载器(保安全) - 再检查是否在
/WEB-INF/classes或已缓存 → 是则自己加载 - 最后才委托给父加载器(Shared → Common)
这个顺序反转,就是隔离的根基:应用自己的类和库,永远有最高优先权。
隔离与共享如何同时成立
靠路径划分 + 加载顺序控制:
-
隔离靠 WebAppClassLoader 独立实例:两个应用里的
com.example.UserController,即使字节码一样,也因加载器不同而成为两个 Class 对象,互不影响 -
共享靠 Common/Shared 层级:servlet-api.jar 只加载一次,所有应用拿到的是同一个
javax.servlet.http.HttpServletRequestClass 对象,类型强转不会失败 -
容器与应用解耦靠 CatalinaClassLoader:Tomcat 内部用的 log4j 配置,不会污染到你的
log4j2.xml
本质上,Tomcat 把「一个 JVM 多个应用」这个现实需求,映射成了多级类加载器的职责切分。


















