1. Tomcat的容器层次结构是什么?
答案:
Tomcat采用分层的容器架构,每一层都实现了Container接口。
容器层次结构:
Server (StandardServer)
└── Service (StandardService)
├── Connector (多个)
└── Engine (StandardEngine)
└── Host (StandardHost - 多个)
└── Context (StandardContext - 多个)
└── Wrapper (StandardWrapper - 多个)
各层容器详解:
1. Server - 服务器实例
- 实现类:
org.apache.catalina.core.StandardServer - 职责:
- 代表整个Catalina servlet容器
- 管理全局资源(JNDI)
- 监听shutdown端口
- 配置:
xml
<Server port="8005" shutdown="SHUTDOWN">
...
</Server>
2. Service - 服务组
- 实现类:
org.apache.catalina.core.StandardService - 职责:
- 将多个Connector与一个Engine关联
- 允许多个协议共享同一组web应用
- 配置:
xml
<Service name="Catalina">
<Connector ... />
<Engine ... />
</Service>
3. Engine - 引擎
- 实现类:
org.apache.catalina.core.StandardEngine - 职责:
- 处理所有Connector的请求
- 包含多个虚拟主机(Host)
- 支持集群和负载均衡
- 配置:
xml
<Engine name="Catalina" defaultHost="localhost">
...
</Engine>
4. Host - 虚拟主机
- 实现类:
org.apache.catalina.core.StandardHost - 职责:
- 代表一个虚拟主机
- 管理web应用的部署
- 支持域名别名
- 配置:
xml
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
...
</Host>
5. Context - Web应用
- 实现类:
org.apache.catalina.core.StandardContext - 职责:
- 代表一个Web应用(ServletContext)
- 管理Servlet、Filter、Listener
- 处理会话管理
- 管理类加载器
- 配置:
xml
<Context path="/myapp" docBase="myapp.war"
reloadable="true">
...
</Context>
6. Wrapper - Servlet包装器
- 实现类:
org.apache.catalina.core.StandardWrapper - 职责:
- 代表一个Servlet定义
- 管理Servlet生命周期
- 处理Servlet的初始化参数
- 管理Servlet实例池(单线程模式)
- 配置: 通过web.xml或注解定义
容器特性:
1. 每个容器都有:
- Pipeline (管道)
- Valve (阀门)
- Lifecycle (生命周期管理)
- Loader (类加载器 - Context级别)
- Manager (会话管理器 - Context级别)
- Realm (安全域)
2. 请求处理流程:
Connector
↓
Engine.Pipeline → Engine.Valve
↓
Host.Pipeline → Host.Valve
↓
Context.Pipeline → Context.Valve
↓
Wrapper.Pipeline → Wrapper.Valve
↓
Servlet.service()
2. Tomcat的生命周期管理机制是什么?
答案:
Tomcat使用统一的生命周期管理机制,所有主要组件都实现Lifecycle接口。
Lifecycle接口
位置: org.apache.catalina.Lifecycle
核心方法:
java
public interface Lifecycle {
void init() throws LifecycleException;
void start() throws LifecycleException;
void stop() throws LifecycleException;
void destroy() throws LifecycleException;
void addLifecycleListener(LifecycleListener listener);
void removeLifecycleListener(LifecycleListener listener);
}
生命周期状态:
NEW (新建)
↓ init()
INITIALIZING (初始化中)
↓
INITIALIZED (已初始化)
↓ start()
STARTING_PREP (启动准备)
↓
STARTING (启动中)
↓
STARTED (已启动)
↓ stop()
STOPPING_PREP (停止准备)
↓
STOPPING (停止中)
↓
STOPPED (已停止)
↓ destroy()
DESTROYING (销毁中)
↓
DESTROYED (已销毁)
生命周期事件:
初始化阶段:
BEFORE_INIT_EVENT- 初始化前AFTER_INIT_EVENT- 初始化后
启动阶段:
BEFORE_START_EVENT- 启动前START_EVENT- 启动时AFTER_START_EVENT- 启动后
停止阶段:
BEFORE_STOP_EVENT- 停止前STOP_EVENT- 停止时AFTER_STOP_EVENT- 停止后
销毁阶段:
BEFORE_DESTROY_EVENT- 销毁前AFTER_DESTROY_EVENT- 销毁后
LifecycleBase实现:
位置: org.apache.catalina.util.LifecycleBase
模板方法模式:
java
public abstract class LifecycleBase implements Lifecycle {
@Override
public final synchronized void init() throws LifecycleException {
// 状态检查
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
// 触发BEFORE_INIT事件
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 调用子类实现
initInternal();
// 触发AFTER_INIT事件
setStateInternal(LifecycleState.INITIALIZED, null, false);
}
protected abstract void initInternal() throws LifecycleException;
protected abstract void startInternal() throws LifecycleException;
protected abstract void stopInternal() throws LifecycleException;
protected abstract void destroyInternal() throws LifecycleException;
}
组件启动顺序:
1. Server.init()
└── Service.init()
├── Engine.init()
│ └── Host.init()
│ └── Context.init()
│ └── Wrapper.init()
└── Connector.init()
2. Server.start()
└── Service.start()
├── Engine.start()
│ └── Host.start()
│ └── Context.start()
│ └── Wrapper.start()
└── Connector.start()
生命周期监听器:
自定义监听器:
java
public class MyLifecycleListener implements LifecycleListener {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
System.out.println("Component started: " + event.getLifecycle());
}
}
}
配置监听器:
xml
<Server>
<Listener className="com.example.MyLifecycleListener" />
</Server>
3. Servlet的生命周期在Tomcat中是如何管理的?
答案:
Servlet的生命周期由Wrapper组件管理。
Servlet生命周期阶段:
1. 加载和实例化
java
// StandardWrapper.loadServlet()
public synchronized Servlet loadServlet() throws ServletException {
// 使用WebappClassLoader加载Servlet类
Class<?> clazz = classLoader.loadClass(servletClass);
// 实例化Servlet
Servlet servlet = (Servlet) clazz.newInstance();
return servlet;
}
触发时机:
- 第一次请求到达时(懒加载)
- 配置了
load-on-startup时在启动时加载
2. 初始化 (init)
java
// StandardWrapper.initServlet()
private synchronized void initServlet(Servlet servlet) throws ServletException {
// 创建ServletConfig
ServletConfig config = new StandardWrapperFacade(this);
// 调用Servlet.init()
servlet.init(config);
// 标记为已初始化
singleThreadModel = servlet instanceof SingleThreadModel;
}
ServletConfig提供:
- Servlet名称
- ServletContext引用
- 初始化参数
3. 服务 (service)
java
// StandardWrapper.allocate()
public Servlet allocate() throws ServletException {
// 单线程模式:从池中获取实例
if (singleThreadModel) {
return pool.pop();
}
// 多线程模式:返回单例
return instance;
}
// StandardWrapperValve.invoke()
public void invoke(Request request, Response response) {
// 分配Servlet实例
Servlet servlet = wrapper.allocate();
// 创建请求和响应门面
ServletRequest req = request.getRequest();
ServletResponse res = response.getResponse();
// 调用Servlet.service()
servlet.service(req, res);
// 释放Servlet实例
wrapper.deallocate(servlet);
}
4. 销毁 (destroy)
java
// StandardWrapper.unload()
public synchronized void unload() throws ServletException {
// 调用Servlet.destroy()
if (instance != null) {
instance.destroy();
instance = null;
}
// 清理单线程模式的实例池
if (singleThreadModel && pool != null) {
while (!pool.isEmpty()) {
pool.pop().destroy();
}
}
}
触发时机:
- Context停止或重新加载
- Tomcat关闭
- Servlet被替换
load-on-startup配置:
web.xml:
xml
<servlet>
<servlet-name>InitServlet</servlet-name>
<servlet-class>com.example.InitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
注解:
java
@WebServlet(name = "InitServlet",
urlPatterns = "/init",
loadOnStartup = 1)
public class InitServlet extends HttpServlet {
// ...
}
加载顺序:
- 值越小越先加载
- 负数表示懒加载
- 相同值的加载顺序不确定
SingleThreadModel (已废弃):
java
public class OldServlet extends HttpServlet implements SingleThreadModel {
// Tomcat会为每个请求创建新实例或使用实例池
}
问题:
- 性能开销大
- 无法保证线程安全(静态变量、外部资源)
- Servlet 2.4已废弃
异步Servlet:
Servlet 3.0+支持:
java
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
AsyncContext asyncContext = req.startAsync();
asyncContext.start(() -> {
// 异步处理
asyncContext.complete();
});
}
}
4. Tomcat的Pipeline和Valve机制是什么?
答案:
Pipeline-Valve是Tomcat实现请求处理链的核心机制,类似于责任链模式。
架构设计:
Pipeline接口:
java
public interface Pipeline {
Valve getBasic();
void setBasic(Valve valve);
void addValve(Valve valve);
Valve[] getValves();
void removeValve(Valve valve);
Valve getFirst();
}
Valve接口:
java
public interface Valve {
Valve getNext();
void setNext(Valve next);
void invoke(Request request, Response response)
throws IOException, ServletException;
}
每个容器的Pipeline:
Engine Pipeline:
StandardEngineValve (Basic Valve)
Host Pipeline:
ErrorReportValve → StandardHostValve (Basic Valve)
Context Pipeline:
NonLoginAuthenticator → StandardContextValve (Basic Valve)
Wrapper Pipeline:
StandardWrapperValve (Basic Valve)
请求处理流程:
1. Connector接收请求
↓
2. Engine.Pipeline.first.invoke()
↓
3. [自定义Valve] → StandardEngineValve
↓ (选择Host)
4. Host.Pipeline.first.invoke()
↓
5. [ErrorReportValve] → StandardHostValve
↓ (选择Context)
6. Context.Pipeline.first.invoke()
↓
7. [AuthenticatorValve] → StandardContextValve
↓ (选择Wrapper)
8. Wrapper.Pipeline.first.invoke()
↓
9. StandardWrapperValve
↓ (调用Servlet)
10. Servlet.service()
StandardEngineValve:
位置: org.apache.catalina.core.StandardEngineValve
java
public void invoke(Request request, Response response) {
// 获取Host
Host host = request.getHost();
if (host == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
// 调用Host的Pipeline
host.getPipeline().getFirst().invoke(request, response);
}
StandardHostValve:
位置: org.apache.catalina.core.StandardHostValve
java
public void invoke(Request request, Response response) {
// 获取Context
Context context = request.getContext();
if (context == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 调用Context的Pipeline
context.getPipeline().getFirst().invoke(request, response);
}
StandardContextValve:
位置: org.apache.catalina.core.StandardContextValve
java
public void invoke(Request request, Response response) {
// 获取Wrapper
Wrapper wrapper = request.getWrapper();
if (wrapper == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 调用Wrapper的Pipeline
wrapper.getPipeline().getFirst().invoke(request, response);
}
StandardWrapperValve:
位置: org.apache.catalina.core.StandardWrapperValve
java
public void invoke(Request request, Response response) {
// 分配Servlet实例
Servlet servlet = wrapper.allocate();
// 创建Filter链
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// 执行Filter链和Servlet
filterChain.doFilter(request.getRequest(), response.getResponse());
// 释放Servlet实例
wrapper.deallocate(servlet);
}
自定义Valve:
实现Valve:
java
public class CustomValve extends ValveBase {
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
// 前置处理
long startTime = System.currentTimeMillis();
// 调用下一个Valve
getNext().invoke(request, response);
// 后置处理
long duration = System.currentTimeMillis() - startTime;
System.out.println("Request took: " + duration + "ms");
}
}
配置Valve:
xml
<Host name="localhost">
<Valve className="com.example.CustomValve" />
</Host>
常用内置Valve:
1. AccessLogValve - 访问日志
xml
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="localhost_access_log"
suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
2. RemoteAddrValve - IP过滤
xml
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
3. ErrorReportValve - 错误报告
xml
<Valve className="org.apache.catalina.valves.ErrorReportValve"
showReport="false"
showServerInfo="false" />
4. RewriteValve - URL重写
xml
<Valve className="org.apache.catalina.valves.rewrite.RewriteValve" />
5. Tomcat的类加载机制是什么?
答案:
Tomcat使用自定义的类加载器层次结构,实现应用隔离和热部署。
类加载器层次:
Bootstrap ClassLoader (JVM)
↓
System ClassLoader (JVM)
↓
Common ClassLoader ($CATALINA_HOME/lib)
↓
├── Server ClassLoader (server classes - 已移除)
└── Shared ClassLoader (shared classes - 可选)
↓
WebappClassLoader (/WEB-INF/classes, /WEB-INF/lib)
↓
Jsp ClassLoader (JSP编译后的类)
WebappClassLoader:
位置: org.apache.catalina.loader.WebappClassLoaderBase
特点:
- 每个Web应用有独立的类加载器
- 实现应用隔离
- 支持热部署
加载顺序 (默认):
- Bootstrap和System类加载器 (Java核心类)
- /WEB-INF/classes (应用类)
- /WEB-INF/lib/*.jar (应用库)
- Common ClassLoader (Tomcat共享库)
委托模式:
默认情况下,WebappClassLoader打破了双亲委派模型:
java
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 1. 检查是否已加载
Class<?> clazz = findLoadedClass(name);
if (clazz != null) {
return clazz;
}
// 2. 系统类委托给父加载器
if (name.startsWith("java.")) {
return parent.loadClass(name);
}
// 3. 尝试自己加载 (打破双亲委派)
try {
clazz = findClass(name);
if (clazz != null) {
return clazz;
}
} catch (ClassNotFoundException e) {
// 继续
}
// 4. 委托给父加载器
return parent.loadClass(name);
}
配置委托模式:
xml
<Context path="/myapp" docBase="myapp.war">
<Loader delegate="true"/>
</Context>
delegate="false"(默认): 先自己加载,再委托父加载器delegate="true": 先委托父加载器,再自己加载(标准双亲委派)
类加载路径:
Common ClassLoader加载:
$CATALINA_HOME/lib$CATALINA_BASE/lib
WebappClassLoader加载:
/WEB-INF/classes/WEB-INF/lib/*.jar
热部署实现:
Context配置:
xml
<Context path="/myapp" docBase="myapp.war" reloadable="true">
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/classes</WatchedResource>
</Context>
工作原理:
- ContainerBackgroundProcessor 定期检查
- 检测到类文件变化
- 调用
Context.reload() - 停止Context
- 销毁旧的WebappClassLoader
- 创建新的WebappClassLoader
- 重新加载所有类
- 启动Context
代码实现:
java
// StandardContext.backgroundProcess()
public void backgroundProcess() {
if (reloadable && modified()) {
reload();
}
}
// StandardContext.reload()
public synchronized void reload() {
// 停止Context
stop();
// 启动Context (会创建新的ClassLoader)
start();
}
ParallelWebappClassLoader:
位置: org.apache.catalina.loader.ParallelWebappClassLoader
特点:
- 支持并行类加载 (Java 7+)
- 提高多线程环境下的加载性能
- 避免死锁
配置:
xml
<Context>
<Loader loaderClass="org.apache.catalina.loader.ParallelWebappClassLoader"/>
</Context>
类加载问题排查:
1. ClassNotFoundException
- 检查类是否在正确的路径
- 检查JAR文件是否损坏
- 检查类加载器委托配置
2. NoClassDefFoundError
- 类的依赖类找不到
- 静态初始化失败
3. ClassCastException
- 同一个类被不同类加载器加载
- 检查是否有重复的JAR
4. LinkageError
- 类被多次加载
- 检查Common和WebApp路径中的重复JAR
6. Tomcat的会话管理机制是什么?
答案:
Tomcat通过Manager组件管理HTTP会话。
Manager接口:
位置: org.apache.catalina.Manager
核心方法:
java
public interface Manager {
Session createSession(String sessionId);
Session findSession(String id) throws IOException;
void remove(Session session);
void add(Session session);
Session[] findSessions();
void load() throws IOException;
void unload() throws IOException;
}
Manager实现类:
1. StandardManager (默认)
特点:
- 会话存储在内存中
- 支持会话持久化到文件
- 单机部署
配置:
xml
<Context>
<Manager className="org.apache.catalina.session.StandardManager"
maxActiveSessions="1000"
sessionIdLength="16" />
</Context>
持久化:
- Tomcat正常关闭时,会话序列化到
SESSIONS.ser - 启动时从文件恢复会话
2. PersistentManager
特点:
- 支持会话持久化到Store
- 支持会话钝化(Passivation)
- 节省内存
配置:
xml
<Context>
<Manager className="org.apache.catalina.session.PersistentManager"
maxActiveSessions="1000"
minIdleSwap="5"
maxIdleSwap="10"
maxIdleBackup="2">
<Store className="org.apache.catalina.session.FileStore"
directory="sessions"/>
</Manager>
</Context>
参数说明:
minIdleSwap: 会话空闲多久后钝化到StoremaxIdleSwap: 会话空闲多久后必须钝化maxIdleBackup: 会话空闲多久后备份到Store
3. DeltaManager (集群)
特点:
- 会话复制到集群所有节点
- 适合小集群(< 4节点)
- 全量复制
配置:
xml
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
</Cluster>
4. BackupManager (集群)
特点:
- 会话只复制到一个备份节点
- 适合大集群
- 节省网络带宽
配置:
xml
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
<Manager className="org.apache.catalina.ha.session.BackupManager"
mapSendOptions="6"/>
</Cluster>
Session实现:
StandardSession:
java
public class StandardSession implements HttpSession, Session, Serializable {
protected String id; // 会话ID
protected long creationTime; // 创建时间
protected long lastAccessedTime; // 最后访问时间
protected int maxInactiveInterval; // 最大不活动时间
protected Map<String, Object> attributes; // 会话属性
protected boolean isValid; // 是否有效
protected Manager manager; // 所属Manager
}
会话ID生成:
SessionIdGenerator:
java
// org.apache.catalina.util.StandardSessionIdGenerator
public String generateSessionId() {
byte[] random = new byte[16];
secureRandom.nextBytes(random);
return toHexString(random);
}
配置:
xml
<Manager sessionIdLength="32">
<SessionIdGenerator className="org.apache.catalina.util.StandardSessionIdGenerator"
sessionIdLength="32"/>
</Manager>
会话超时:
配置超时时间:
web.xml:
xml
<session-config>
<session-timeout>30</session-timeout> <!-- 分钟 -->
</session-config>
Context.xml:
xml
<Context sessionTimeout="30"/>
程序设置:
java
session.setMaxInactiveInterval(1800); // 秒
超时检查:
java
// StandardManager.backgroundProcess()
public void backgroundProcess() {
Session[] sessions = findSessions();
for (Session session : sessions) {
if (session.isValid() && session.isExpired()) {
session.expire();
}
}
}
会话监听器:
HttpSessionListener:
java
@WebListener
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("Session created: " + se.getSession().getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("Session destroyed: " + se.getSession().getId());
}
}
HttpSessionAttributeListener:
java
@WebListener
public class SessionAttributeListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("Attribute added: " + se.getName());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("Attribute removed: " + se.getName());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
System.out.println("Attribute replaced: " + se.getName());
}
}
Cookie配置:
Context.xml:
xml
<Context>
<CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"
sameSiteCookies="strict"/>
</Context>
web.xml:
xml
<session-config>
<cookie-config>
<name>MYSESSIONID</name>
<domain>.example.com</domain>
<path>/</path>
<http-only>true</http-only>
<secure>true</secure>
<max-age>3600</max-age>
</cookie-config>
</session-config>