做了几年开发,这个技术点一直是面试高频考点,今天系统梳理一下核心知识点和实战经验。

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端口
  • 配置:
<Server port="8005" shutdown="SHUTDOWN">
  ...
</Server>

2. Service - 服务组

  • 实现类:org.apache.catalina.core.StandardService
  • 职责:
    • 将多个Connector与一个Engine关联
    • 允许多个协议共享同一组web应用
  • 配置:
<Service name="Catalina">
  <Connector ... />
  <Engine ... />
</Service>

3. Engine - 引擎

  • 实现类:org.apache.catalina.core.StandardEngine
  • 职责:
    • 处理所有Connector的请求
    • 包含多个虚拟主机(Host)
    • 支持集群和负载均衡
  • 配置:
<Engine name="Catalina" defaultHost="localhost">
  ...
</Engine>

4. Host - 虚拟主机

  • 实现类:org.apache.catalina.core.StandardHost
  • 职责:
    • 代表一个虚拟主机
    • 管理web应用的部署
    • 支持域名别名
  • 配置:
<Host name="localhost" appBase="webapps"
  unpackWARs="true" autoDeploy="true">
...
</Host>

5. Context - Web应用

  • 实现类:org.apache.catalina.core.StandardContext
  • 职责:
    • 代表一个Web应用(ServletContext)
    • 管理Servlet、Filter、Listener
    • 处理会话管理
    • 管理类加载器
  • 配置:
<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

核心方法:

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

模板方法模式:

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()

生命周期监听器:

自定义监听器:

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());
}
}
}

配置监听器:

<Server>
  <Listener className="com.example.MyLifecycleListener" />
</Server>

3. Servlet的生命周期在Tomcat中是如何管理的?

答案:

Servlet的生命周期由Wrapper组件管理。

Servlet生命周期阶段:

1. 加载和实例化

// 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)

// 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)

// 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)

// 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:

<servlet>
<servlet-name>InitServlet</servlet-name>
<servlet-class>com.example.InitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

注解:

@WebServlet(name = "InitServlet",
urlPatterns = "/init",
loadOnStartup = 1)
public class InitServlet extends HttpServlet {
// ...
}

加载顺序:

  • 值越小越先加载
  • 负数表示懒加载
  • 相同值的加载顺序不确定

SingleThreadModel (已废弃):

public class OldServlet extends HttpServlet implements SingleThreadModel {
// Tomcat会为每个请求创建新实例或使用实例池
}

问题:

  • 性能开销大
  • 无法保证线程安全(静态变量、外部资源)
  • Servlet 2.4已废弃

异步Servlet:

Servlet 3.0+支持:

@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接口:

public interface Pipeline {
Valve getBasic();
void setBasic(Valve valve);
void addValve(Valve valve);
Valve[] getValves();
void removeValve(Valve valve);
Valve getFirst();
}

Valve接口:

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

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

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

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

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:

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:

<Host name="localhost">
  <Valve className="com.example.CustomValve" />
</Host>

常用内置Valve:

1. AccessLogValve - 访问日志

<Valve className="org.apache.catalina.valves.AccessLogValve"
  directory="logs"
  prefix="localhost_access_log"
  suffix=".txt"
  pattern="%h %l %u %t &quot;%r&quot; %s %b" />

2. RemoteAddrValve - IP过滤

最佳实践:

经过多个项目的验证,我总结了几个关键点:1) 做好异常处理 2) 添加详细日志 3) 单元测试覆盖核心逻辑。 这些看似简单,但能避免很多生产环境问题。

<Valve className="org.apache.catalina.valves.RemoteAddrValve"
  allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />

3. ErrorReportValve - 错误报告

<Valve className="org.apache.catalina.valves.ErrorReportValve"
  showReport="false"
  showServerInfo="false" />

4. RewriteValve - URL重写

<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应用有独立的类加载器
  • 实现应用隔离
  • 支持热部署

加载顺序 (默认):

  1. Bootstrap和System类加载器 (Java核心类)
  2. /WEB-INF/classes (应用类)
  3. /WEB-INF/lib/*.jar (应用库)
  4. Common ClassLoader (Tomcat共享库)

委托模式:

默认情况下,WebappClassLoader打破了双亲委派模型:

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);
    }

配置委托模式:

<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配置:

<Context path="/myapp" docBase="myapp.war" reloadable="true">
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/classes</WatchedResource>
</Context>

工作原理:

  1. ContainerBackgroundProcessor 定期检查
  2. 检测到类文件变化
  3. 调用 Context.reload()
  4. 停止Context
  5. 销毁旧的WebappClassLoader
  6. 创建新的WebappClassLoader
  7. 重新加载所有类
  8. 启动Context

代码实现:

// 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+)
  • 提高多线程环境下的加载性能
  • 避免死锁

配置:

<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

核心方法:

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 (默认)

特点:

  • 会话存储在内存中
  • 支持会话持久化到文件
  • 单机部署

配置:

<Context>
    <Manager className="org.apache.catalina.session.StandardManager"
    maxActiveSessions="1000"
    sessionIdLength="16" />
</Context>

持久化:

  • Tomcat正常关闭时,会话序列化到 SESSIONS.ser
  • 启动时从文件恢复会话

2. PersistentManager

特点:

  • 支持会话持久化到Store
  • 支持会话钝化(Passivation)
  • 节省内存

配置:

<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: 会话空闲多久后钝化到Store
  • maxIdleSwap: 会话空闲多久后必须钝化
  • maxIdleBackup: 会话空闲多久后备份到Store

3. DeltaManager (集群)

特点:

  • 会话复制到集群所有节点
  • 适合小集群(< 4节点)
  • 全量复制

配置:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
    <Manager className="org.apache.catalina.ha.session.DeltaManager"
    expireSessionsOnShutdown="false"
    notifyListenersOnReplication="true"/>
</Cluster>

4. BackupManager (集群)

特点:

  • 会话只复制到一个备份节点
  • 适合大集群
  • 节省网络带宽

配置:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
    <Manager className="org.apache.catalina.ha.session.BackupManager"
    mapSendOptions="6"/>
</Cluster>

Session实现:

StandardSession:

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:

// org.apache.catalina.util.StandardSessionIdGenerator
public String generateSessionId() {
byte[] random = new byte[16];
secureRandom.nextBytes(random);
return toHexString(random);
}

配置:

<Manager sessionIdLength="32">
    <SessionIdGenerator className="org.apache.catalina.util.StandardSessionIdGenerator"
    sessionIdLength="32"/>
</Manager>

会话超时:

配置超时时间:

web.xml:

<session-config>
<session-timeout>30</session-timeout> <!-- 分钟 -->
</session-config>

Context.xml:

<Context sessionTimeout="30"/>

程序设置:

session.setMaxInactiveInterval(1800); // 秒

超时检查:

// StandardManager.backgroundProcess()
public void backgroundProcess() {
Session[] sessions = findSessions();
for (Session session : sessions) {
if (session.isValid() && session.isExpired()) {
session.expire();
}
}
}

会话监听器:

HttpSessionListener:

@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:

@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:

<Context>
    <CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"
    sameSiteCookies="strict"/>
</Context>

web.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>