異常處理是 Java 程序健壯性的基石,但實際項目中濫用、吞噬、丟失異常的現象屢見不鮮。本文系統梳理 五種標準處理模式 與 三大經典反模式,配合代碼對比與最佳實踐,助你寫出既安全又可維護的異常代碼。
public void methodA() {
try {
methodB(); // 調用拋出異常的方法
} catch (BusinessException e) {
// 統一處理:日誌、轉換、重試、熔斷
log.error("業務失敗", e);
throw new ServiceException("處理失敗", e);
}
}
public void methodB() throws BusinessException {
if (invalid) throw new BusinessException("參數非法");
// 正常邏輯
}
適用:底層無法處理、需上層統一決策的場景(DAO → Service → Controller)。
public void methodA() {
try {
riskyOperation();
} catch (IOException e) {
// 具體恢復邏輯:重試、降級、默認值
log.warn("讀取配置失敗,使用默認值", e);
loadDefaults();
}
}
適用:異常可預期、有明確恢復路徑、無需外層感知的場景。
public void methodB() throws BusinessException {
try {
risky();
} catch (RecoverableException e) {
handleRecoverable(e); // 內部消化
}
// 其他異常(如 BusinessException)自動向上拋出
}
關鍵:catch 僅處理特定子類,父類或其他異常繼續拋出。
public void methodB() throws BusinessException {
Resource res = acquire();
try {
use(res);
} finally {
release(res); // 無論是否異常,必執行
}
}
現代寫法:try-with-resources (JDK 7+) 自動調用 AutoCloseable.close()。
public void methodB() throws ServiceException {
try {
jdbcTemplate.update(sql);
} catch (DataAccessException e) {
// 將底層技術異常轉為業務異常,隱藏實現細節
throw new ServiceException("數據庫操作失敗", e);
}
}
原則:保留原始異常作為 cause,保證堆棧追蹤完整。
// ❌ 錯誤
public void methodB() throws BusinessException {
try {
risky();
} catch (BusinessException e) {
log.error("捕獲", e);
throw e; // 或 throw new BusinessException(e.getMessage()); // 丟失堆棧
}
}
// ✅ 正確:若無處理邏輯,直接不 catch,讓其自然拋出
public void methodB() throws BusinessException {
risky();
}
// ❌ 錯誤:finally 的 return 會覆蓋 try 的 return,並吞噬 try 中拋出的異常
public int methodB() {
try { return 1; }
finally { return 2; } // 永遠返回 2
}
// ✅ 正確:finally 僅做清理,不返回值
public int methodB() {
try { return 1; }
finally { cleanup(); }
}
// ❌ 錯誤:Exception 是所有檢查型異常父類,後續 catch 永不可達
try { risky(); }
catch (Exception e) { handleGeneric(e); }
catch (IOException e) { handleIo(e); } // Dead code
// ✅ 正確:具體異常在前,通用異常在後
try { risky(); }
catch (IOException e) { handleIo(e); }
catch (SQLException e) { handleSql(e); }
catch (Exception e) { handleGeneric(e); }
發生異常
│
├─ 能在當前層「有意義地處理」嗎?(恢復/降級/記錄並繼續)
│ ├─ 能 → catch 處理,不再拋出
│ └─ 不能 →
│ ├─ 能「轉換為更高層語義的異常」嗎?(技術→業務)
│ │ ├─ 能 → catch 轉換拋出 (保留 cause)
│ │ └─ 不能 →
│ │ ├─ 能「添加上下文信息後重新拋出」嗎?
│ │ │ ├─ 能 → catch 包裝拋出
│ │ │ └─ 不能 → 在方法簽名 throws,由調用者處理
│ │
└─ 是否需要「無論如何都要執行清理」?
├─ 是 → finally / try-with-resources
└─ 否 → 無需 finally
BaseException → BizException / SysException,Controller 僅捕獲 BaseException 統一響應。ErrorCode,前端據此國際化提示。ERROR 級別記錄堆棧,WARN 級別記錄業務預期異常(如參數校驗失敗)。catch { } 空塊。try-with-resources 或 finally 釋放。@throws 標註拋出條件與含義。異常處理的核心不是「捕獲」,而是「流向控制」:讓異常流向最能處理它的層級,並在流經的關鍵節點完成資源清理與語義轉換。內化五大模式、杜絕三大反模式,配合統一異常體系與切面日誌,即可構建清晰、可追蹤、可運維的 Java 異常防禦體系。