JSP/Servlet 應用的異常處理應遵循「就近處理、分層攔截、統一展示」原則:
JSTL 核心標籤庫提供 `
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<c:catch var="exception">
<!-- 可能拋出異常的代碼:EL 表達式、標籤、Scriptlet -->
<c:out value="${user.profile.address}" />
<%= riskyHelper.generateReport() %>
</c:catch>
<c:if test="${exception != null}">
<div class="alert warning">
局部功能異常:${exception.message}
<c:if test="${pageContext.request.isUserInRole('admin')}">
<pre>${exception.stackTrace}</pre>
</c:if>
</div>
</c:if>
<%@ page contentType="text/html;charset=UTF-8" isErrorPage="true" %>
<!DOCTYPE html>
<html>
<head><title>系統錯誤</title></head>
<body>
<h1>頁面加載失敗</h1>
<p>發生時間:${pageContext.errorData.timestamp}</p>
<p>請求 URI:${pageContext.errorData.requestURI}</p>
<p>異常類型:${pageContext.errorData.throwable.class.name}</p>
<p>異常信息:${pageContext.errorData.throwable.message}</p>
<c:if test="${pageContext.request.isUserInRole('admin')}">
<details><summary>堆疊追蹤</summary>
<pre>${pageContext.errorData.throwable.stackTrace}</pre>
</details>
</c:if>
<a href="javascript:history.back()">返回上一頁</a> |
<a href="${pageContext.request.contextPath}/">回首頁</a>
</body>
</html>
關鍵點:`isErrorPage="true"` 使隱含對象 `exception` (即 `pageContext.errorData.throwable`) 可用。
<%@ page contentType="text/html;charset=UTF-8" errorPage="/error/generic.jsp" %>
<!-- 或者動態指定 (罕用) -->
<% response.setStatus(500); %>
當該 JSP 執行過程中拋出未捕獲異常,容器自動轉發至 `/error/generic.jsp`,原請求屬性保留,URL 不變 (服務端轉發)。
| 對象 | 類型 | 說明 |
|---|---|---|
| exception | Throwable | 拋出的異常實例 (僅 isErrorPage=true 可用) |
| pageContext.errorData | ErrorData | 封裝異常、請求 URI、Servlet 名、時間戳 |
| request | HttpServletRequest | 原請求,保留參數、屬性、Session |
| response | HttpServletResponse | 響應對象,狀態碼已設為 500 |
部署描述符配置異常類型、HTTP 狀態碼到錯誤頁的映射,覆蓋所有 Servlet、Filter、JSP、靜態資源請求。
<web-app xmlns="https://xmlns.jcp.org/xml/ns/javaee" version="4.0">
<!-- 按異常類型映射 (支援繼承關係) -->
<error-page>
<exception-type>java.lang.NullPointerException</exception-type>
<location>/error/null-pointer.jsp</location>
</error-page>
<error-page>
<exception-type>com.shop.exception.BizException</exception-type>
<location>/error/business.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error/system.jsp</location>
</error-page>
<!-- 按 HTTP 狀態碼映射 -->
<error-page>
<error-code>404</error-code>
<location>/error/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.jsp</location>
</error-page>
<error-page>
<error-code>403</error-code>
<location>/error/403.jsp</location>
</error-page>
</web-app>
@WebServlet(value = "/app/*",
initParams = @WebInitParam(name = "errorPage", value = "/error/system.jsp"))
public class AppServlet extends HttpServlet { ... }
// 或在 ServletContainerInitializer 中程序化註冊
context.addServlet("app", AppServlet.class)
.setErrorPages(Collections.singleton(new ErrorPage(Exception.class, "/error/system.jsp")));
請求進入
│
├─► Filter 鏈 (編碼、登入檢查、日誌)
│ │
│ ├─ 異常 → web.xml error-page (403/500/Exception)
│ │
│ ▼
├─► Servlet / Controller
│ │
│ ├─ 捕獲異常 → 業務邏輯處理 / 拋出 BizException
│ │
│ ▼
├─► JSP 頁面渲染
│ │
│ ├─ 捕獲局部異常 → 就近展示降級內容
│ │
│ ├─ 未捕獲異常 → page指令 errorPage 轉發
│ │
│ ▼
└─► 響應輸出
│
└─ 容器級異常 (如 JSP 編譯錯誤、Filter 初始化失敗) → web.xml error-page
JSP 異常處理三層體系:`