現象: out.println(user.getName()),若 getName() 回傳 null,輸出字面字串 "null",而非拋出 NullPointerException。
風險: 後續字串操作(substring、equals、length())將拋出 NPE,除錯困難。
解決:
<%-- 安全輸出 --%>
<%= user.getName() != null ? user.getName() : "" %>
<%-- 或 JSTL --%>
<c:out value="${user.name}" default="未填寫" />
典型代碼:
if (request.getParameter("username").equals("admin")) { ... } // 參數不存在 → NPE
out.println(session.getAttribute("user")); // 屬性不存在 → 輸出 null,後續操作崩潰
防禦性編程模板:
String username = request.getParameter("username");
if (username != null && username.equals("admin")) { ... }
Object userObj = session.getAttribute("user");
if (userObj != null) {
User user = (User) userObj;
// 安全使用
}
工具類封裝: StringUtils.isNotBlank()、Objects.requireNonNull() (Java 7+)。
場景: int age = Integer.parseInt(request.getParameter("age"));,使用者輸入「abc」或空值。
解決:
try {
int age = Integer.parseInt(request.getParameter("age"));
} catch (NumberFormatException e) {
// 記錄日誌、提示用戶、設定預設值
request.setAttribute("error", "年齡必須為數字");
forwardToErrorPage(req, resp);
}
進階: 使用 Scanner、Apache Commons NumberUtils、Spring TypeConverter 統一參數綁定與驗證。
原因: str.substring(0, 10) 但 str.length() < 10;str.charAt(5) 但 str == null。
解決:
public static String safeSubstring(String str, int begin, int end) {
if (str == null) return "";
int len = str.length();
if (begin < 0) begin = 0;
if (end > len) end = len;
if (begin >= end) return "";
return str.substring(begin, end);
}
// 或轉換為 byte[] 處理二進制安全
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
建議: 將常用字串操作封裝至 StringUtil JavaBean,JSP 呼叫 StringUtil.truncate(str, 100)。
原因: .class 檔案放置位置不符合容器規範;JAR 包缺失或版本衝突。
標準放置規則:
| 類型 | 放置路徑 |
|---|---|
| 專案類別 | WEB-INF/classes/套件/類別.class |
| 第三方 JAR | WEB-INF/lib/*.jar |
| 共享庫 (JDBC 驅動等) | Tomcat lib/*.jar (舊版 common/lib) |
排查步驟: 確認類檔案存在 → 套件名稱正確 → 無重複 JAR 衝突 → 重啟容器。
核心原因: 資料庫連線、檔案流、網路 Socket、Statement/ResultSet 未關閉,導致連線池耗盡、檔案描述符耗盡、GC 壓力劇增。
鐵律:獲取資源 → try 使用 → finally 關閉
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
// 處理結果
} finally {
// 逆序關閉,單獨 try-catch 避免前一個關閉失敗阻塞後續
try { if (rs != null) rs.close(); } catch (SQLException ignore) {}
try { if (pstmt != null) pstmt.close(); } catch (SQLException ignore) {}
try { if (conn != null) conn.close(); } catch (SQLException ignore) {}
}
Java 7+ Try-with-resources (推薦):
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
// 自動關閉,順序正確,異常安全
}
RequestUtil.getString(req, "param", "default") 內建 null/空值處理。${empty param.name ? '預設' : param.name} 內建空值安全。JdbcTemplate、MyBatis、Hibernate 自動處理關閉。web.xml <error-page> + 全域異常攔截器,避免堆疊洩露。getConnection()、new FileInputStream()、HttpClient.execute() 都有對應 close()。JSP 開發六大陷阱本質上是「防禦性編程缺失」與「資源管理不當」。將驗證、轉換、資源控制封裝至工具類與框架層,JSP 退回純粹視圖角色,是根治之道。現代開發中,拒絕在 JSP 寫 Java 代碼,擁抱 Spring MVC + Thymeleaf/FreeMarker 或前後端分離架構,從源頭消除此類問題。