应定义特定业务异常类而非直接throw RuntimeException,因其能区分系统错误与业务拒绝,支持统一错误码、用户提示、调试详情及HTTP状态码,并通过@ControllerAdvice统一处理,避免污染API和破坏模块边界。
为什么不能直接 throw new RuntimeException(“订单不存在”)
业务异常不是程序 bug,混用 RuntimeException 会导致调用方无法区分「系统崩溃」和「业务拒绝」。比如支付接口返回 500 还是 400,前端重试策略、监控告警、日志分级都依赖这个边界。一旦所有异常都塞进 RuntimeException,下游只能靠字符串匹配错误信息,脆弱且不可维护。
实操建议:
- 为每类业务场景定义独立的异常类,如
OrderNotFoundException、InsufficientBalanceException - 继承
RuntimeException(非检查异常),避免强制 try-catch 干扰正常流程 - 构造函数至少支持
String message和String code两个参数,code 用于统一错误码体系(如 “ORDER_NOT_FOUND”) - 不要在异常类里放业务逻辑方法(如
getSuggestAction()),它只负责携带上下文
自定义异常该带哪些字段才够用
光有 message 和 code 不够。真实场景中,前端需要提示语,运营需要查问题,风控需要原始参数——这些都得靠异常体本身透出,而不是靠日志拼接或全局上下文隐式传递。
推荐字段组合:
立即学习“Java免费学习笔记(深入)”;
-
code:静态错误码(大写+下划线),如"PAY_AMOUNT_MISMATCH" -
message:面向用户的提示(可含占位符,如"支付金额不匹配:期望{expected},收到{actual}") -
details:Map类型,存原始请求 ID、订单号、金额等关键调试字段 -
httpStatus:int 类型,默认 400,特殊场景可设 401/403/422/500
示例:
public class PaymentValidationException extends RuntimeException {
private final String code;
private final Map details;
private final int httpStatus;
public PaymentValidationException(String code, String message, Map details) {
super(message);
this.code = code;
this.details = Collections.unmodifiableMap(details);
this.httpStatus = 422;
}
}
如何让 Controller 层自动转换异常为标准 JSON 响应
每个接口都写 try-catch 太重复,Spring 的 @ControllerAdvice + @ExceptionHandler 是标准解法,但要注意细节。