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 异常处理 的技巧,不仅能避免系统崩溃,还能通过友好的提示提升用户信任度。
异常处理的核心目标是:
- 捕获异常:在代码中识别潜在的错误场景。
- 记录异常:将错误信息保存以便后续排查。
- 响应用户:向客户端返回有意义的反馈,而非技术性错误。
Servlet 中的常见异常类型
在 Servlet 生命周期中,开发者会遇到多种异常类型。以下列举常见异常及其触发场景:
异常类型 | 触发场景 |
---|---|
ServletException | 由 Servlet 容器或框架抛出,通常因配置错误或逻辑问题导致。 |
IOException | 在输入输出操作(如读取请求体、写入响应)中发生,例如网络中断或文件未找到。 |
NullPointerException | 访问未初始化的对象或空指针引用时触发。 |
NumberFormatException | 将字符串转换为数字时格式不匹配,例如用户输入非数字字符。 |
SQLException | 数据库操作失败,如连接断开或 SQL 语法错误。 |
异常处理的比喻:交通系统中的安全网
将 Servlet 比作一条繁忙的高速公路:
- 异常如同车辆抛锚或交通事故。
- try-catch 块是应急车道,允许车辆(程序)临时停靠并处理问题。
- 全局异常处理是交警指挥系统,统一协调大面积的交通异常。
直接处理异常:try-catch 块
在 Servlet 方法(如 doGet
、doPost
)中,可通过 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
会增加代码冗余。通过 Filter 或 web.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()
只是基础操作。推荐集成 Log4j 或 SLF4J 等日志框架,将异常信息记录到文件或集中式日志系统。
示例 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);
}
}
}
最佳实践与常见误区
推荐做法
- 分层处理异常:在业务层捕获具体异常,Servlet 层处理通用异常。
- 保持响应简洁:避免将堆栈跟踪暴露给普通用户。
- 测试异常场景:编写单元测试模拟异常触发条件。
需避免的错误
- 忽略 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 应用在复杂场景下始终可靠运行。