public class AppRuntimeException extends RuntimeException {
private final String errorCode; // 業務錯誤碼,前端可國際化
public AppRuntimeException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public AppRuntimeException(String errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public String getErrorCode() { return errorCode; }
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired private DataSource dataSource;
public void insert(User user) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
// ... 執行 SQL
conn.commit();
} catch (SQLException e) {
// ① 事務回滾
if (conn != null) try { conn.rollback(); } catch (SQLException ex) { log.error("Rollback failed", ex); }
// ② 轉換拋出(保留原始堆棧)
throw new AppRuntimeException("DB_ERROR", "數據庫操作失敗", e);
} finally {
// ③ 關閉資源(或使用 try-with-resources)
if (conn != null) try { conn.close(); } catch (SQLException ex) { log.error("Close failed", ex); }
}
}
}
@Service
@Transactional // 聲明式事務,自動回滾 RuntimeException
public class UserServiceImpl implements UserService {
@Autowired private UserDao userDao;
public void register(User user) {
try {
// 業務校驗
if (userDao.exists(user.getUsername())) {
throw new AppRuntimeException("USER_EXISTS", "用戶名已存在");
}
userDao.insert(user);
// 其他業務邏輯:發送歡迎郵件、初始化配置等
} catch (AppRuntimeException e) {
// 業務異常直接向上拋出(@Transactional 會回滾)
throw e;
} catch (Exception e) {
// 未預期異常包裝為系統錯誤
throw new AppRuntimeException("SYS_ERROR", "系統內部錯誤", e);
}
}
}
<error-page>
<exception-type>com.example.AppRuntimeException</exception-type>
<location>/error/business.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.RuntimeException</exception-type>
<location>/error/system.jsp</location>
</error-page>
| 陷阱 | 後果 | 正確做法 |
|---|---|---|
| DAO 捕獲 SQLException 後不拋出/吞噬 | 事務不回滾、上層感知不到失敗 | 回滾後 throw new AppRuntimeException(..., e) |
| Service 層 catch Exception 後 log 並 return null | 事務不回滾(非 RuntimeException)、調用方收到 null 導致 NPE | 包裝為 RuntimeException 子類拋出 |
| 反射調用時目標異常被 InvocationTargetException 包裹 | web.xml 無法匹配原始異常類型 | ControllerAdvice 中解包 e.getCause() 判斷真實類型 |
| 錯誤頁面顯示堆棧信息 | 信息洩露、用戶體驗差 | 生產環境僅顯示友好提示,日誌記錄完整堆棧 |
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(AppRuntimeException.class)
public R<Void> handleBiz(AppRuntimeException e) {
return R.fail(e.getErrorCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public R<Void> handleSys(Exception e) {
log.error("系統異常", e);
return R.fail("SYS_ERROR", "系統繁忙");
}
}
// 統一響應封裝
public class R<T> {
private int code; private String msg; private T data;
public static <T> R<T> fail(String code, String msg) { ... }
}
分層異常處理的核心是「各司其職、層層轉換、統一入口」:DAO 负責資源清理與事務回滾,Service 封裝業務語義,Web 層統一映射到用戶可感知的反饋。配合 @ControllerAdvice/@RestControllerAdvice 實現零侵入的全局異常治理,是構建健壯 JavaEE 應用的基石。