Servlet 容器(Tomcat/Jetty)默認拋出 500/404 等錯誤時,顯示容器自帶的醜陋堆棧頁面,洩露服務器信息,用戶體驗極差,且無法記錄完整上下文便於排查。
<error-page> <error-code>404</error-code> <location>/WEB-INF/views/error/404.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/WEB-INF/views/error/500.jsp</location> </error-page> <error-page> <exception-type>java.lang.Exception</exception-type> <location>/WEB-INF/views/error/500.jsp</location> </error-page>
error-code:匹配 HTTP 狀態碼。exception-type:匹配異常類型(含子類),優先級高於 error-code。location:必須以 / 開頭,建議放在 WEB-INF 下防止直接訪問。在錯誤 JSP 中,容器自動設置以下請求屬性(可通過 ${requestScope['key']} 或 request.getAttribute() 獲取):
| 屬性鍵 | 說明 |
|---|---|
| javax.servlet.error.status_code | HTTP 狀態碼 (Integer) |
| javax.servlet.error.exception_type | 異常類名 (Class) |
| javax.servlet.error.message | 異常消息 (String) |
| javax.servlet.error.exception | 異常對象 (Throwable) |
| javax.servlet.error.request_uri | 出錯請求 URI (String) |
| javax.servlet.error.servlet_name | 出錯 Servlet 名稱 (String) |
<%@ page pageEncoding="UTF-8" isErrorPage="true" %>
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>系統錯誤</title>
<style>body{font-family: sans-serif; max-width: 800px; margin: 2rem auto; padding: 1rem;} pre{background:#f5f5f5; padding:1rem; overflow:auto;}</style>
</head>
<body>
<h1>抱歉,系統發生錯誤</h1>
<p>狀態碼:${requestScope['javax.servlet.error.status_code']}</p>
<p>異常類型:${requestScope['javax.servlet.error.exception_type']}</p>
<p>異常信息:${requestScope['javax.servlet.error.message']}</p>
<p>請求路徑:${requestScope['javax.servlet.error.request_uri']}</p>
<c:if test="${not empty requestScope['javax.servlet.error.exception']}">
<h3>堆棧跟蹤</h3>
<pre>${requestScope['javax.servlet.error.exception'].stackTrace}</pre>
</c:if>
<p><a href="${pageContext.request.contextPath}">返回首頁</a> | <a href="javascript:history.back()">上一頁</a></p>
</body>
</html>
Spring MVC 推薦使用 @ControllerAdvice + @ExceptionHandler 實現全局異常處理,返回 JSON 或視圖,避免 web.xml 配置分散:
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public ModelAndView handleException(HttpServletRequest request, Exception ex) {
log.error("請求 {} 發生異常", request.getRequestURI(), ex);
ModelAndView mv = new ModelAndView("error/500");
mv.addObject("exception", ex);
mv.addObject("url", request.getRequestURI());
return mv;
}
@ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView handle404(HttpServletRequest request) {
return new ModelAndView("error/404");
}
}
GlobalExceptionHandler 或錯誤 JSP 中,將異常信息(堆棧、請求參數、用戶 ID、時間戳)寫入日誌文件/數據庫/ELK。若用戶禁用 Cookie,容器無法透過 JSESSIONID Cookie 識別 Session。解決方案:對所有輸出 URL 執行 response.encodeURL() / encodeRedirectURL(),自動附加 ;jsessionid=xxx。
<a href="<%= response.encodeURL("/shop/cart") %>">購物車</a>
response.sendRedirect(response.encodeRedirectURL("/login"));
web.xml 或 @ControllerAdvice 覆蓋 404/500/異常類型。