
自定义异常类必须继承Exception或RuntimeException,提供三个标准构造函数,不重写getMessage()和getCause(),命名体现业务语义而非技术细节,并配合@ControllerAdvice统一处理。
Exception 或 RuntimeException
Java 中抛出自定义异常的前提是它得是 Throwable 的子类。绝大多数业务异常应继承 Exception(编译期检查异常),强制调用方处理;若属于程序逻辑错误(如参数非法、状态不一致),可继承 RuntimeException(运行时异常),避免处处 try-catch 或声明 throws。
常见错误是直接继承 Throwable 或空参构造不调用父类构造,导致堆栈信息丢失或序列化失败。
Exception:适合需显式处理的业务场景,如支付超时、库存不足RuntimeException:适合开发阶段应避免发生的错误,如 IllegalArgumentException 的语义延伸MyException()、MyException(String message)、MyException(String message, Throwable cause)
getMessage() 和 getCause() 不要被覆盖成空逻辑自定义异常类中,除非有明确封装需求(如脱敏敏感字段),否则不要重写 getMessage() 或 getCause()。JVM 日志、IDE 调试、监控系统都依赖这些方法返回原始信息。
例如,以下写法会掩盖真实错误原因:
public class PaymentException extends Exception {
public PaymentException(String message) {
super(message);
}
@Override
public String getMessage() {
return "系统繁忙,请稍后再试"; // ❌ 隐藏原始 message,调试困难
}
}
正确做法是保留原始信息,必要时在日志中补充上下文:
super(message, cause) 传递原始异常链catch 块中用 log.error("支付失败,订单ID: {}", orderId, e) 补充业务字段命名反映语义层级,而非技术路径。比如 InsufficientBalanceException 比 AccountBalanceCheckFailedException 更清晰;OrderAlreadyShippedException 比 UpdateOrderStatusException 更准确。
容易踩的坑:
ValidateUserException)—— 这是动作,不是状态JDBCConnectionTimeoutException)——
应抽象为业务语义,如 DatabaseUnavailableException
InvldParamEx 或 BusinessError)—— 无法快速定位问题域@ControllerAdvice 统一处理更实用单独定义异常类意义有限,关键在于如何与框架协同。Spring MVC 中,自定义异常配合全局异常处理器能统一响应格式、HTTP 状态码和日志记录。
典型结构:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(InsufficientStockException.class)
@ResponseStatus(HttpStatus.PRECONDITION_FAILED)
@ResponseBody
public ApiResponse handleInsufficientStock(InsufficientStockException e) {
return ApiResponse.fail("库存不足", "STOCK_SHORTAGE");
}
}
注意点:
@ExceptionHandler 方法签名中精确匹配(不建议捕获 Exception 后再 instanceof 判断)@ExceptionHandler 中抛出新异常,否则可能绕过统一处理逻辑ResponseEntityExceptionHandler,优先复用其默认行为,再针对性扩展真正难的不是写一个异常类,而是让整个团队对每个异常的触发条件、恢复方式和日志埋点达成一致。名字、构造方式、是否检查、在哪捕获——这些决策一旦分散,就会变成排查时的噪音源。