Servlet 异常处理(长文讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

异常处理的重要性与基础概念

在开发 Web 应用程序时,Servlet 作为核心组件之一,其异常处理能力直接影响系统的稳定性与用户体验。想象一下,当用户提交表单后,服务器因未处理的异常崩溃,用户只能看到空白页面或错误代码,这样的体验显然糟糕透顶。因此,掌握 Servlet 异常处理 的技巧,不仅能避免系统崩溃,还能通过友好的提示提升用户信任度。

异常处理的核心目标是:

  1. 捕获异常:在代码中识别潜在的错误场景。
  2. 记录异常:将错误信息保存以便后续排查。
  3. 响应用户:向客户端返回有意义的反馈,而非技术性错误。

Servlet 中的常见异常类型

在 Servlet 生命周期中,开发者会遇到多种异常类型。以下列举常见异常及其触发场景:

异常类型触发场景
ServletException由 Servlet 容器或框架抛出,通常因配置错误或逻辑问题导致。
IOException在输入输出操作(如读取请求体、写入响应)中发生,例如网络中断或文件未找到。
NullPointerException访问未初始化的对象或空指针引用时触发。
NumberFormatException将字符串转换为数字时格式不匹配,例如用户输入非数字字符。
SQLException数据库操作失败,如连接断开或 SQL 语法错误。

异常处理的比喻:交通系统中的安全网

将 Servlet 比作一条繁忙的高速公路:

  • 异常如同车辆抛锚或交通事故。
  • try-catch 块是应急车道,允许车辆(程序)临时停靠并处理问题。
  • 全局异常处理是交警指挥系统,统一协调大面积的交通异常。

直接处理异常:try-catch 块

在 Servlet 方法(如 doGetdoPost)中,可通过 try-catch 块直接捕获并处理异常。

示例 1:处理数据库连接异常

protected void doPost(HttpServletRequest request, HttpServletResponse response) {  
    try {  
        // 建立数据库连接  
        Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);  
        // 执行 SQL 操作  
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";  
        PreparedStatement pstmt = conn.prepareStatement(sql);  
        pstmt.setString(1, request.getParameter("name"));  
        pstmt.setString(2, request.getParameter("email"));  
        pstmt.executeUpdate();  
    } catch (SQLException e) {  
        // 记录错误日志  
        e.printStackTrace();  
        // 向用户返回友好提示  
        response.setContentType("text/html");  
        response.getWriter().write("数据库连接失败,请稍后再试。");  
    }  
}  

注意事项

  • 避免空 catch 块catch (Exception e) {} 会隐藏错误,导致调试困难。
  • 按需捕获:优先捕获具体异常(如 SQLException),而非父类 Exception

全局异常处理:Filter 与 Web 配置

直接在每个方法中写 try-catch 会增加代码冗余。通过 Filterweb.xml 配置,可以实现全局异常捕获。

方法 1:使用 Filter 拦截异常

public class GlobalExceptionFilter implements Filter {  
    @Override  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
        try {  
            chain.doFilter(request, response);  
        } catch (Exception e) {  
            // 统一处理异常逻辑  
            handleException(e, (HttpServletResponse) response);  
        }  
    }  

    private void handleException(Exception e, HttpServletResponse resp) {  
        resp.setContentType("text/html");  
        resp.getWriter().write("系统发生错误,请联系管理员!");  
        // 记录日志到文件或数据库  
        Logger.getLogger(GlobalExceptionFilter.class.getName()).log(Level.SEVERE, null, e);  
    }  
}  

web.xml 中注册 Filter:

<filter>  
    <filter-name>GlobalExceptionFilter</filter-name>  
    <filter-class>com.example.GlobalExceptionFilter</filter-class>  
</filter>  
<filter-mapping>  
    <filter-name>GlobalExceptionFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping>  

方法 2:通过 web.xml 指定错误页面

web.xml 中配置全局错误页面:

<error-page>  
    <exception-type>java.lang.Exception</exception-type>  
    <location>/error.jsp</location>  
</error-page>  

当未捕获的异常发生时,浏览器将自动跳转到 /error.jsp,开发者可在该页面中展示自定义提示。

结合日志框架:增强调试能力

直接使用 e.printStackTrace() 只是基础操作。推荐集成 Log4jSLF4J 等日志框架,将异常信息记录到文件或集中式日志系统。

示例 2:使用 SLF4J 记录日志

import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  

public class UserController extends HttpServlet {  
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);  

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {  
        try {  
            // 业务逻辑代码  
        } catch (Exception e) {  
            logger.error("用户查询失败", e);  
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);  
        }  
    }  
}  

最佳实践与常见误区

推荐做法

  1. 分层处理异常:在业务层捕获具体异常,Servlet 层处理通用异常。
  2. 保持响应简洁:避免将堆栈跟踪暴露给普通用户。
  3. 测试异常场景:编写单元测试模拟异常触发条件。

需避免的错误

  • 忽略 finally 块:未释放资源(如数据库连接、文件流),可能导致资源泄漏。
  • 过度泛化异常类型:如捕获 Exception 而不具体处理,可能掩盖关键问题。

案例分析:登录功能异常处理

假设一个登录表单提交后,若用户输入无效密码,需返回提示而非崩溃。

前端页面(login.html)

<form action="/login" method="post">  
    <input type="text" name="username" placeholder="用户名">  
    <input type="password" name="password" placeholder="密码">  
    <button type="submit">登录</button>  
</form>  

后端 Servlet(LoginServlet.java)

protected void doPost(HttpServletRequest req, HttpServletResponse resp) {  
    String username = req.getParameter("username");  
    String password = req.getParameter("password");  

    try {  
        if (!authService.authenticate(username, password)) {  
            throw new InvalidCredentialsException("用户名或密码错误");  
        }  
        // 登录成功逻辑  
    } catch (InvalidCredentialsException e) {  
        req.setAttribute("error", "登录失败:" + e.getMessage());  
        RequestDispatcher dispatcher = req.getRequestDispatcher("/login.jsp");  
        dispatcher.forward(req, resp);  
    } catch (SQLException e) {  
        // 数据库异常处理逻辑  
        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);  
    }  
}  

错误页面(login.jsp)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>  
<html>  
<head><title>登录失败</title></head>  
<body>  
    <p style="color: red;"><%= request.getAttribute("error") %></p>  
    <a href="/login">返回登录页</a>  
</body>  
</html>  

结论

Servlet 异常处理是构建健壮 Web 应用程序的基石。通过合理使用 try-catch、Filter、日志框架及配置错误页面,开发者既能保障系统稳定性,又能为用户提供清晰的反馈。记住,异常不是程序的终点,而是优化代码的起点。

在实际开发中,建议结合代码审查与自动化测试,持续完善异常处理逻辑。只有将异常管理融入开发习惯,才能让您的 Web 应用在复杂场景下始终可靠运行。

最新发布