Tomcat 是一个开源的 Java Servlet 容器,用于运行 Java Web 应用程序。它的类加载机制相对复杂,因为它需要在支持多种应用的同时保持隔离性和灵活性。以下是 Tomcat 类加载机制的详细描述。
Tomcat 类加载器的层次结构
Tomcat 采用了一种层次化的类加载器结构,以便在不同的应用程序之间实现类加载的隔离,同时也允许共享一些公共类库。Tomcat 的类加载器层次结构如下:
-
Bootstrap ClassLoader
- 这是 JVM 自带的类加载器,负责加载 Java 核心类库(如
rt.jar
)。
- 这是 JVM 自带的类加载器,负责加载 Java 核心类库(如
-
System ClassLoader
- 也称为应用程序类加载器,加载
$JAVA_HOME/lib/ext
和classpath
中的类。
- 也称为应用程序类加载器,加载
-
Common ClassLoader
- 由 Tomcat 自定义,加载
CATALINA_HOME/lib
和CATALINA_BASE/lib
中的类库。
- 由 Tomcat 自定义,加载
-
Catalina ClassLoader
- 加载
CATALINA_HOME/lib
和CATALINA_BASE/lib
中的类库,通常与 Common ClassLoader 合并使用。
- 加载
-
Shared ClassLoader
- 加载
CATALINA_BASE/shared/lib
中的类库,可供所有 Web 应用程序共享。
- 加载
-
Webapp ClassLoader
- 每个 Web 应用都有自己的类加载器,加载
WEB-INF/classes
和WEB-INF/lib
中的类库。Tomcat 为每个 Web 应用程序创建一个新的 Webapp ClassLoader 实例。
- 每个 Web 应用都有自己的类加载器,加载
类加载过程
- Bootstrap ClassLoader 首先加载 Java 核心类库。
- System ClassLoader 加载系统级别的类库。
- Common ClassLoader 加载 Tomcat 公共库。
- Catalina ClassLoader 加载与 Tomcat 运行时相关的类库。
- Shared ClassLoader 加载所有 Web 应用程序共享的类库。
- Webapp ClassLoader 最后加载各自应用程序的类库。此类加载器是独立的,确保不同 Web 应用程序之间的类隔离。
Webapp ClassLoader 的双亲委派模型
Webapp ClassLoader 使用双亲委派模型,但其委派顺序有所不同。一般的双亲委派模型是:子 -> 父 -> 根。而 Tomcat 的 Webapp ClassLoader 的委派顺序是:子 -> 父 -> 共享 -> 公共 -> 根。这是为了确保 Web 应用程序首先加载自己的类,而不是公共类库中的类。
典型的类加载顺序
当一个 Web 应用请求加载一个类时,Tomcat 的类加载器会按照以下顺序尝试加载:
- Webapp ClassLoader:首先检查 Web 应用的
WEB-INF/classes
和WEB-INF/lib
中是否有该类。 - Shared ClassLoader:如果 Webapp ClassLoader 没有找到,则委派给 Shared ClassLoader。
- Common ClassLoader:如果 Shared ClassLoader 也没有找到,则委派给 Common ClassLoader。
- System ClassLoader:如果 Common ClassLoader 也没有找到,则委派给 System ClassLoader。
- Bootstrap ClassLoader:最后由 Bootstrap ClassLoader 尝试加载。
破坏双亲委派模型
在某些情况下,可能需要打破双亲委派模型,以实现某些特定的功能。Tomcat 提供了几种方式:
- 自定义类加载器:通过创建自定义的类加载器,可以在特定的类加载场景中打破双亲委派模型。
- 使用
server.xml
配置文件:可以在server.xml
文件中配置<Loader>
元素,自定义 Web 应用的类加载器行为。 - 在 Web 应用中显式地使用不同的类加载器:例如,通过使用
Thread.currentThread().getContextClassLoader()
来获取当前线程的类加载器,并在应用代码中显式地使用它加载类。
示例:自定义 Web 应用类加载器
以下是一个简单的自定义 Web 应用类加载器的示例,展示了如何在 Tomcat 中打破双亲委派模型:
package com.example;
import java.net.URL;
import java.net.URLClassLoader;
public class CustomWebappClassLoader extends URLClassLoader {
public CustomWebappClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 打破双亲委派模型,首先尝试加载自己定义的类
synchronized (getClassLoadingLock(name)) {
// 检查是否已经加载过该类
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 尝试从当前类加载器中加载
c = findClass(name);
} catch (ClassNotFoundException e) {
// 如果当前类加载器无法加载,则委派给父类加载器
c = super.loadClass(name, resolve);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
在 context.xml
或 server.xml
中配置自定义类加载器:
<Context path="/myapp" docBase="webapps/myapp" reloadable="true">
<Loader loaderClass="com.example.CustomWebappClassLoader"/>
</Context>
总结
Tomcat 的类加载机制通过层次化的类加载器结构和双亲委派模型,确保了类加载的隔离性和安全性。然而,在某些特殊情况下,可能需要打破双亲委派模型,以实现特定的功能或需求。通过自定义类加载器和配置文件,开发者可以灵活地控制类加载行为。