1. 简介

本文将带你掌握在 Jakarta EE Servlet 应用中如何优雅地处理异常,确保系统出错时能返回预期、友好的响应,而不是把堆栈直接暴露给用户——这在生产环境中是大忌,踩坑无数。

我们不会引入 Spring 或其他框架,纯粹基于原生 Servlet API 实现,适合想深入理解底层机制的开发者。

2. Jakarta EE Servlet 异常示例

先定义一个简单的 Servlet,使用 @WebServlet 注解注册接口路径。它的 doGet 方法会主动抛出一个异常,用于模拟运行时错误:

@WebServlet(urlPatterns = "/randomError")
public class RandomErrorServlet extends HttpServlet {

    @Override
    protected void doGet(
      HttpServletRequest req, 
      HttpServletResponse resp) {
        throw new IllegalStateException("Random error");
    }
}

这个接口 /randomError 一旦被访问,就会触发 IllegalStateException。接下来我们看看默认行为是什么。

3. 默认异常处理机制

将应用部署到 Servlet 容器(比如 Tomcat),假设应用上下文路径为:http://localhost:8080/jakarta-servlets

访问 http://localhost:8080/jakarta-servlets/randomError,你会看到类似下面的默认错误页面:

servlet

✅ 这个页面由 Servlet 容器(如 Tomcat)自动生成。
⚠️ 缺点很明显:信息过于详细,可能泄露内部实现,存在安全风险。
📌 默认行为可通过容器级别配置(如 Tomcat 的 ErrorReportValve)或应用级别 web.xml 覆盖。

4. 自定义异常处理

为了提升用户体验和安全性,我们需要自定义错误处理逻辑。可以通过 web.xml 配置两种类型的错误映射:

  • 状态码映射:针对特定 HTTP 状态码(如 404、500)跳转到指定页面或 Servlet
  • 异常类型映射:捕获特定异常类,并交由指定组件处理

4.1 使用 HTML 页面处理状态码异常

比如我们想为 404 错误提供一个友好的提示页,可以在 web.xml 中配置:

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns="http://java.sun.com/xml/ns/javaee"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">

    <error-page>
        <error-code>404</error-code>
        <location>/error-404.html</location> <!-- 文件路径: /src/main/webapp/error-404.html -->
    </error-page>

</web-app>

只要请求的资源不存在(返回 404),容器就会自动跳转到 /error-404.html。你可以在该页面中设计品牌化的错误提示,提升产品质感。

4.2 使用 Servlet 处理异常类型

更灵活的方式是使用一个专门的 ErrorHandlerServlet 来处理异常。比如我们希望捕获所有 Exception 类型的异常:

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns="http://java.sun.com/xml/ns/javaee"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">
    <error-page> 
        <exception-type>java.lang.Exception</exception-type> 
        <location>/errorHandler</location> 
    </error-page>
</web-app>

然后实现对应的 ErrorHandlerServlet,通过请求中的标准错误属性获取上下文信息:

@WebServlet(urlPatterns = "/errorHandler")
public class ErrorHandlerServlet extends HttpServlet {

    @Override
    protected void doGet(
      HttpServletRequest req, 
      HttpServletResponse resp) throws IOException {
 
        resp.setContentType("text/html; charset=utf-8");
        try (PrintWriter writer = resp.getWriter()) {
            writer.write("<html><head><title>Error description</title></head><body>");
            writer.write("<h2>Error description</h2>");
            writer.write("<ul>");
            Arrays.asList(
              ERROR_STATUS_CODE, 
              ERROR_EXCEPTION_TYPE, 
              ERROR_MESSAGE)
              .forEach(e ->
                writer.write("<li>" + e + ":" + req.getAttribute(e) + " </li>")
            );
            writer.write("</ul>");
            writer.write("</html></body>");
        }
    }
}

现在访问 http://localhost:8080/jakarta-servlets/randomError,你会看到由 ErrorHandlerServlet 渲染的错误详情页。

📌 关键点:

  • ERROR_STATUS_CODEERROR_EXCEPTION_TYPEERROR_MESSAGE 是 Servlet 规范定义的标准请求属性
  • ✅ 可通过 req.getAttribute(ERROR_EXCEPTION) 获取原始异常对象
  • ❌ 示例中捕获 java.lang.Exception 太宽泛,实际项目中应细化到具体异常类型,避免误伤

日志记录建议

ErrorHandlerServlet 中,推荐使用容器提供的日志机制记录异常,便于排查:

Exception exception = (Exception) req.getAttribute(ERROR_EXCEPTION);
if (IllegalArgumentException.class.isInstance(exception)) {
    getServletContext()
      .log("Error on an application argument", exception);
}

⚠️ 注意:ServletContext.log() 是基础日志功能,生产环境建议集成 SLF4J + Logback 或 Log4j2 实现更强大的日志控制。

5. 总结

本文演示了 Jakarta EE 原生 Servlet 中的异常处理机制:

  • ✅ 默认错误页由容器生成,不适合生产使用
  • ✅ 可通过 web.xml 配置状态码或异常类型的自定义处理逻辑
  • ✅ 使用 ErrorHandlerServlet 能实现高度定制化的错误响应
  • ✅ 结合 ServletContext.log() 实现基本日志追踪

整个过程无需引入第三方框架,简单粗暴但足够有效。适合轻量级项目或作为深入理解 Java Web 机制的起点。

示例代码已托管至 Servlets 教程仓库
邮箱支持:support@baeldung.com


原始标题:Jakarta EE Servlet Exception Handling

« 上一篇: Java Weekly, 第234期
» 下一篇: JPA 中的乐观锁机制