tomcat 实现多应用隔离的核心是精准控制加载顺序和命名空间边界,每个应用独享 webappclassloader,同名类因加载器不同而互不可见;采用本地优先加载策略,支持同名库不同版本共存;通过分层类加载器复用共享类,并借助线程上下文类加载器保障框架正确加载用户代码。

Tomcat 实现多应用隔离,核心不是“绕开”类加载机制,而是**精准控制加载顺序和命名空间边界**。每个 Web 应用在 JVM 中拥有独立的类加载器实例,同名类因加载器不同而天然互不可见——这是隔离的底层基础。
每个应用独享一个 WebAppClassLoader
Tomcat 启动时,为每个部署的应用(如 app1、app2)创建专属的 WebAppClassLoader 实例。JVM 判定两个 Class 是否相同,需同时满足:全限定名一致 + 加载器实例相同。因此:
-
app1/WEB-INF/classes/com/example/User.class被其 WebAppClassLoader 加载 → 生成 Class A -
app2/WEB-INF/classes/com/example/User.class被另一个 WebAppClassLoader 加载 → 生成 Class B - Class A 和 Class B 在 JVM 中是完全独立的对象,静态变量、初始化块、反射结果均不共享
本地优先加载:改变“谁先试”的逻辑
WebAppClassLoader 重写了 loadClass(String, boolean) 方法,执行三步策略:
- 先查本应用已加载缓存(
findLoadedClass) - 再扫描
/WEB-INF/classes目录下的 .class 文件 - 再遍历
/WEB-INF/lib/下所有 JAR 包 - 仅当以上全部失败,才调用
super.loadClass委托给父加载器
这种“先本地、后委派”设计,让 app1 可自带 slf4j-api-1.7.30.jar,app2 可自带 slf4j-api-2.0.9.jar,互不影响。
共享类由父加载器统一供给
不是所有类都要隔离。像 javax.servlet.Servlet、org.springframework.context.ApplicationContext 这类通用依赖,通过分层加载器复用:
-
CommonClassLoader:加载
$CATALINA_HOME/lib/,供 Tomcat 内核与所有应用共享(如 Servlet API) -
SharedClassLoader(可选):加载
$CATALINA_HOME/shared/lib/,供多个应用共用工具包 - WebAppClassLoader 在本地找不到时自动委托,确保 Spring 等类只加载一次,避免内存浪费
线程上下文类加载器保障框架可用性
Spring、JDBC 驱动等容器级框架由 CatalinaClassLoader 加载,本身看不到 Web 应用里的类。Tomcat 在处理请求时会临时设置:
Thread.currentThread().setContextClassLoader(webAppClassLoader)
这样 Spring 就能通过 Thread.currentThread().getContextClassLoader().loadClass("com.myapp.ServiceImpl") 正确加载用户代码,不会抛 ClassNotFoundException。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











