JSP 异常处理
page 指令的 errorPage、isErrorPage、web.xml 全局错误页、以及日志。
页面级
<%-- business.jsp:本页出错就去 500.jsp --%>
<%@ page contentType="text/html;charset=UTF-8"
errorPage="/WEB-INF/errors/500.jsp" %>
<%
int n = Integer.parseInt(request.getParameter("n"));
%><%-- 500.jsp:我是错误页 --%>
<%@ page isErrorPage="true" contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html><body>
<h1>页面出错了</h1>
<p><%= exception.getClass().getSimpleName() %>: <%= exception.getMessage() %></p>
</body></html>A relative errorPage path is resolved against the current page; a leading / means the application root. Keeping it under WEB-INF stops users from opening the error page directly.
应用级
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/errors/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errors/500.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.NumberFormatException</exception-type>
<location>/WEB-INF/errors/bad-param.jsp</location>
</error-page>Page-level config affects one page; application-level catches everything unhandled. Real projects use both: a global safety net plus finer control on specific pages.
错误页能拿到什么
<%@ page isErrorPage="true" contentType="text/html;charset=UTF-8" %>
<%
Object status = request.getAttribute("jakarta.servlet.error.status_code");
Object msg = request.getAttribute("jakarta.servlet.error.message");
Object uri = request.getAttribute("jakarta.servlet.error.request_uri");
Throwable ex = (Throwable) request.getAttribute("jakarta.servlet.error.exception");
if (ex != null) {
// 只写日志,不输出到页面
getServletContext().log("页面异常: " + uri, ex);
}
%>
<!DOCTYPE html>
<html><body>
<h1>出错了</h1>
<p>错误编号:<%= status %></p>
<p><a href="<%= request.getContextPath() %>/">返回首页</a></p>
</body></html>exception.printStackTrace(response.getWriter()) exposes class names, package layout, SQL and file paths to users. Show an id and a friendly message; keep details in the log.try-catch 还是抛出去
| Situation | Handle where |
|---|---|
| 参数格式错 | 本页 try/catch + sendError(400) |
| 资源不存在 | 本页 sendError(404) |
| 数据库异常 | 记日志后抛给容器,走 500 页 |
| 代码 bug(NPE 等) | 不要 catch,让它 500 并暴露在日志里 |
Catching an NPE and showing "system busy" hides the bug: the user sees a normal message and the log stays empty, so the defect is never found. Handle only exceptions you know how to handle.
页面级
<%-- business.jsp:本页出错就去 500.jsp --%>
<%@ page contentType="text/html;charset=UTF-8"
errorPage="/WEB-INF/errors/500.jsp" %>
<%
int n = Integer.parseInt(request.getParameter("n"));
%><%-- 500.jsp:我是错误页 --%>
<%@ page isErrorPage="true" contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html><body>
<h1>页面出错了</h1>
<p><%= exception.getClass().getSimpleName() %>: <%= exception.getMessage() %></p>
</body></html>errorPage 的路径如果是相对写法,是相对于当前页面;以 / 开头则是相对于应用根。放在 WEB-INF 下可以防止用户直接访问错误页。
应用级
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/errors/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errors/500.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.NumberFormatException</exception-type>
<location>/WEB-INF/errors/bad-param.jsp</location>
</error-page>页面级配置只影响那一个页面;应用级配置兜底所有未处理异常。实际项目两处都配:全局兜底,个别页面再精细控制。
错误页能拿到什么
<%@ page isErrorPage="true" contentType="text/html;charset=UTF-8" %>
<%
Object status = request.getAttribute("jakarta.servlet.error.status_code");
Object msg = request.getAttribute("jakarta.servlet.error.message");
Object uri = request.getAttribute("jakarta.servlet.error.request_uri");
Throwable ex = (Throwable) request.getAttribute("jakarta.servlet.error.exception");
if (ex != null) {
// 只写日志,不输出到页面
getServletContext().log("页面异常: " + uri, ex);
}
%>
<!DOCTYPE html>
<html><body>
<h1>出错了</h1>
<p>错误编号:<%= status %></p>
<p><a href="<%= request.getContextPath() %>/">返回首页</a></p>
</body></html>exception.printStackTrace(response.getWriter()) 写进错误页,等于把类名、包结构、SQL、文件路径全公开给用户。错误页只显示编号和友好提示,细节进日志。try-catch 还是抛出去
| 情况 | 处理方式 |
|---|---|
| 参数格式错 | 本页 try/catch + sendError(400) |
| 资源不存在 | 本页 sendError(404) |
| 数据库异常 | 记日志后抛给容器,走 500 页 |
| 代码 bug(NPE 等) | 不要 catch,让它 500 并暴露在日志里 |
把 NPE 也 catch 掉然后显示「系统繁忙」,等于把 bug 藏起来——用户看到的是正常提示,日志里什么都没有,问题永远查不出来。只处理你知道怎么处理的异常。