不该直接 throw new RuntimeException("xxx"),因其导致日志泛化、无法差异化处理、IDE无提示三大问题;自定义异常须重写四个构造函数;应通过枚举 BusinessErrorCode 管理错误码;Spring 中需确保扫描路径、@Order 及异步异常传递正确。

为什么不该直接 throw new RuntimeException("xxx")
业务场景里抛出 RuntimeException 看似省事,但实际埋了三个坑:一是日志里全是泛化的 RuntimeException,无法区分是参数错、权限错还是库存不足;二是上层统一捕获时没法做差异化处理(比如参数错返回 400,权限错跳登录页);三是 IDE 和静态检查工具对裸 RuntimeException 几乎不提示,容易漏掉关键异常分支。
自定义业务异常类必须重写哪些构造函数
只写一个带 String 的构造函数远远不够。Spring MVC、Feign、甚至某些序列化框架在透传异常时,会调用不同签名的构造函数,漏掉任意一个都可能导致 message 丢失或堆栈错乱。
必须显式提供以下四个构造函数:
public BusinessException(String message) {
super(message);
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public BusinessException(Throwable cause) {
super(cause);
}
public BusinessException() {
super();
}
尤其注意带 Throwable cause 的两个重载——链路追踪(如 SkyWalking)和日志上下文传递全靠它。
要不要加错误码字段?怎么设计才不耦合
加,但别用 int 或 String 硬编码。错误码本质是业务语义,不是数字本身。直接暴露 errorCode 字段会让调用方误以为可以 switch-case 判断,反而破坏封装。
推荐做法:
- 定义枚举
BusinessErrorCode,每个枚举项包含code、message和可选的httpStatus - 异常类持有一个
BusinessErrorCode枚举引用,而非原始值 - 通过
getErrorCode()方法暴露,内部逻辑可随时改为动态计算或查配置中心
这样前端取码用 exception.getErrorCode().getCode(),后端日志打印自动带语义,改码也不用 grep 全局字符串。
Spring Boot 中如何让自定义异常被 @ControllerAdvice 正确捕获
常见陷阱是继承了 RuntimeException 却没生效,大概率因为:① 异常类没被 Spring 扫描到(不在启动类同包或子包);② @ControllerAdvice 类没加 @Order 且存在多个 Advice 冲突;③ 抛出位置在异步线程(如 @Async)中,主线程的 Advice 捕不到。
验证和修复要点:
- 确保异常类在
@SpringBootApplication扫描路径下 -
@ControllerAdvice类加上@Order(Ordered.HIGHEST_PRECEDENCE)避免被其他 Advice 拦截 - 异步场景必须手动把异常 re-throw 到主线程,或改用
CompletableFuture的handle()处理 - 测试时用
MockMvc而非直接 new Controller,否则 Advice 不触发
最隐蔽的问题:某些 RPC 框架(如 Dubbo)默认只透传 Exception 子类,RuntimeException 会被静默吞掉——这时得配 dubbo.provider.throws=java.lang.RuntimeException。










