不能只用 IllegalArgumentException 或 RuntimeException,因其缺乏业务语义,导致日志难区分、调用方无法精准处理、监控无法聚合、前端无法差异化提示;自定义异常需含 errorCode、traceId(推荐)、业务字段(如orderId),并设置serialVersionUID以保障跨JVM序列化。
为什么不能只用 IllegalArgumentException 或 RuntimeException?
因为它们不带业务语义。比如用户注册时手机号已存在,抛出 IllegalArgumentException("phone exists"),和参数为空、格式错误混在一起,日志里根本分不清是校验失败还是业务冲突。调用方也无法针对性重试或降级——所有异常都 catch RuntimeException,等于没区分。
- 同一个异常类型可能在 10 个模块里被抛出,堆栈看不出问题归属
- 监控系统无法按异常类型聚合告警(比如想单独看“库存不足”异常飙升)
-
前端需要不同错误码提示(400-参数错 / 409-资源冲突),通用异常没法携带
errorCode
继承 Exception 还是 RuntimeException?关键看是否强制处理
选错父类会导致编译报错或逻辑失控。比如订单创建失败必须通知用户并回滚事务,就该用受检异常(Exception 子类),强迫调用方显式处理;而参数校验失败属于编程错误,用 RuntimeException 子类更合理,避免满屏 try-catch。
- 用
Exception:外部依赖异常(如支付回调超时)、必须恢复的业务中断(如库存扣减失败) - 用
RuntimeException:输入校验、状态非法(如“订单已发货却要取消”)、内部逻辑断言 - 别让 Controller 层 catch 受检异常再包装成运行时异常——这会绕过编译检查,失去设计初衷
自定义异常里至少要加什么字段?
光有 message 不够。真实线上问题中,你永远需要快速定位“谁、在什么场景、因什么数据触发了这个异常”。所以建议标配三样:
-
errorCode:整型错误码,用于前端展示或网关路由(如USER_NOT_FOUND = 1001) -
traceId(可选但强烈推荐):关联全链路日志,避免翻几十个服务日志找上下文 - 必要业务字段:比如
orderId、skuId,直接打在异常对象里,比拼接字符串安全且易提取
示例:new OrderAlreadyShippedException("Order already shipped", 2003, "ORD-20260204-XXXX", "SKU-888")
立即学习“Java免费学习笔记(深入)”;