自定义异常需设计合理字段并确保被消费:errorcode(枚举字符串)、usermessage(国际化提示)、context(调试上下文),支持格式化构造与原因包装,通过public getter序列化,配合全局处理器输出结构化响应、日志及前端适配。

自定义异常要携带丰富的业务状态数据,关键不是往异常里塞字段,而是让这些字段能被后续环节(比如全局处理器、日志系统、前端响应)真正用起来。核心在于字段设计合理、构造可控、序列化安全、响应可映射。
字段设计:按用途分层,不堆砌
业务状态数据不是越多越好,而是要区分“谁看”和“用来干啥”:
-
errorCode:字符串类型(如
"ORDER_CANCELLED"),全局唯一、语义明确,建议用枚举管理,避免硬编码 -
userMessage:面向用户的友好提示(如
"订单已取消,无法再次支付"),不带技术细节,支持国际化占位符({orderId}) -
debugInfo / context:Map 或封装类,存放调试所需上下文(如
orderId=12345、userId=67890、remainingStock=2),用于日志追踪或后台排查 - timestamp / requestId:可选,便于链路对齐;若已有 MDC 或网关透传,异常中不必重复存
构造方式:支持格式化与原因包装
光有字段不够,得方便业务代码快速构建——构造器要覆盖常见场景:
- 仅传
errorCode和userMessage(最常用) - 带
context参数的重载,自动将 Map 转为不可变副本,防止外部修改 - 支持传入原始异常
cause,用于包装底层异常(如数据库超时、远程调用失败),保留完整堆栈 - 内部用
String.format()或 SLF4J 风格("库存不足,当前剩余: {}, 需求: {}")解析userMessage,避免拼接字符串
序列化与 JSON 响应适配
Spring Boot 默认把异常转 JSON 时,只序列化继承链上的字段。要确保业务字段能透出,注意三点:
- 所有业务字段必须是 public getter 方法(如
getErrorCode()),且方法名符合 JavaBean 规范 - 避免在字段上加
@JsonIgnore或@Transient,除非明确不想暴露 - 如果用了 Lombok,用
@Data或显式写@Getter,别只依赖@AllArgsConstructor - 全局异常处理器返回的
ResponseEntity或统一响应对象,应直接读取异常实例的 getter,而不是靠反射提取私有字段
配合全局处理器落地价值
字段再丰富,不被消费就是摆设。典型做法是在 @ExceptionHandler 中结构化输出:
- HTTP 状态码根据业务严重程度设定(如参数错误用 400,资源不存在用 404,权限问题用 403)
- 响应体包含
code、message、data(null)、debugInfo(仅开发环境或特定 profile 下返回) - WARN 级日志记录时,把
errorCode和context打进 MDC,方便 ELK 搜索 - 前端可根据
code做差异化处理(如跳转、弹窗、重试),不用解析模糊 message
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











