在成熟 Web 框架下開發業務代碼,只關注 RuntimeException(非受檢異常) 是常態。框架層應封裝 Checked Exception(受檢異常)轉為 Unchecked Exception,業務層拋出自定義業務異常,最後由統一異常映射器轉換為標準 HTTP 響應,實現:
try-catch 干擾| 層級 | 異常處理策略 | 示例 |
|---|---|---|
| 框架/基礎設施層 | 捕獲 Checked Exception → 包裝為自定義 Unchecked Exception | DAO 層 IOException → DaoException |
| 業務邏輯層 | 直接拋出自定義 RuntimeException 子類 | 參數校驗失敗 → ServiceException |
| 接口適配層 | 無業務 try-catch,異常向上拋出 | Jersey Resource 方法不捕獲異常 |
| 統一異常映射層 | 實現 ExceptionMapper<Exception> 統一轉換響應 | 返回 400/404/500 + 國際化錯誤碼 |
public class BaseException extends RuntimeException {
private String code; // 國際化鍵
private Object[] values; // 佔位符參數
public BaseException(String code, Throwable cause, Object[] values) {
super(code, cause);
this.code = code;
this.values = values;
}
// getter/setter 省略
}
// DAO 層異常
public class DaoException extends BaseException {
public DaoException(Throwable cause, String code) {
super(code, cause, null);
}
}
// Service 層異常
public class ServiceException extends BaseException {
public ServiceException(String code) {
super(code, null, null);
}
}
# errMsg.properties
read.file.failed=文件讀取失敗
read.config.failed=配置項讀取失敗
must.be.less.than.10=參數必須小於10
request.not.found=請求資源不存在
internal.server.error=服務器內部錯誤
@Provider
public class ExceptionMapperSupport implements ExceptionMapper<Exception> {
@Context HttpServletRequest request;
@Context ServletContext servletContext;
private static final Logger LOG = Logger.getLogger(ExceptionMapperSupport.class);
@Override
public Response toResponse(Exception ex) {
String msgKey = "internal.server.error";
Response.Status status = Response.Status.INTERNAL_SERVER_ERROR;
// 1. 業務異常:提取 code 國際化
if (ex instanceof BaseException) {
BaseException be = (BaseException) ex;
msgKey = be.getCode();
// 此處結合 Spring MessageSource 獲取國際化文本
// String msg = messageSource.getMessage(msgKey, be.getValues(), request.getLocale());
}
// 2. Jersey 404
else if (ex instanceof NotFoundException) {
msgKey = "request.not.found";
status = Response.Status.NOT_FOUND;
}
// 3. 其他未知異常(含 Checked Exception 泄露)
// 保持 500,記錄完整堆棧
// 統一日誌記錄
LOG.error("異常處理: " + msgKey, ex);
// 統一響應格式
return Response.status(status)
.entity(msgKey) // 實際項目返回結構化錯誤對象
.type(MediaType.TEXT_PLAIN)
.build();
}
}
@Component
public class TestDao {
public String sayHello() {
try (InputStream is = getClass().getResourceAsStream("/test.properties")) {
Properties props = new Properties();
props.load(is);
return props.getProperty("key1") + "," + props.getProperty("key2");
} catch (IOException e) {
// Checked Exception → Unchecked Exception
throw new DaoException(e, "read.file.failed");
}
}
}
GET /resources/test?n=11 → 觸發 ServiceException("must.be.less.than.10") → 返回 400 Bad Request + 國際化提示DaoException → 返回 500 Internal Server Error + 日誌含完整堆棧NotFoundException → 返回 404 Not Foundnew XxxException(e, code)),防止信息丟失。異常處理的本質是「失控時的可控性」:讓錯誤可見、可查、可恢復、可度量。