BizException必须继承RuntimeException,因其属业务异常而非程序错误,需避免强制捕获污染代码;DAO层不得抛BizException,应由Service层根据返回结果判断业务规则并抛出。

Java里BizException该继承RuntimeException还是Exception
业务异常必须是 unchecked(不强制捕获),否则调用方会疯狂写 try-catch 或抛 throws,污染所有业务层代码。所以 BizException 必须继承 RuntimeException,不是 Exception。
常见错误:有人为了“强制提醒处理”而继承 Exception,结果导致 Service 方法签名满屏 throws BizException,Controller 层被迫层层透传,最终和系统异常混在一起,全局异常处理器根本分不清哪是业务拒绝、哪是数据库挂了。
- 继承
RuntimeException:符合“业务规则失败不属程序故障”的语义,也匹配 Spring 的@ExceptionHandler默认捕获策略 - 别加
serialVersionUID:除非你真要跨 JVM 序列化它——业务异常不该被序列化传输 - 构造函数只保留
String message和String message, Throwable cause两个即可,避免过度设计
为什么不能在DAO层抛BizException
DAO 层只负责数据存取,它不知道“库存不足”是业务规则,它只看到 UPDATE product SET stock = stock - 1 WHERE id = ? AND stock >= 1 影响行数为 0。这个“影响行数为 0”应该由 Service 层判断后,再决定是否抛 BizException。
典型翻车现场:DAO 方法里写 if (rows == 0) throw new BizException("库存扣减失败"),结果单元测试一跑就报错——因为 DAO 层不该持有业务语义,更不该触发全局异常拦截(比如被 @RestControllerAdvice 拦住返回 500,而不是 400)。
立即学习“Java免费学习笔记(深入)”;
- DAO 返回 int / boolean / Optional / 结果对象,让上层决定语义
- Service 方法内做状态校验:
if (product.getStock() - MyBatis 的
<select>或 JPA 的Optional<T>查询不到时,也绝不直接抛业务异常
如何让BizException携带错误码和HTTP状态码
前端需要明确的 code(如 "ORDER_NOT_FOUND")和 HTTP 状态码(如 404),但 BizException 本身是 Java 异常,不能直接塞 HTTP 语义。正确做法是:异常类只管业务标识,状态码交给全局处理器映射。
错误做法:在 BizException 里加 httpStatus 字段,然后全局处理器硬编码 switch;这样一旦新增异常类型就得改处理器,耦合死。
-
BizException只含code: String和message: String(后者可走 i18n) - 定义枚举
BizCode,每个值对应一个code字符串和建议的 HTTP 状态码(如ORDER_PAID(409)) - 全局异常处理器中,根据
exception.getCode()查BizCode.valueOf(code)拿状态码,不依赖异常实例字段 - 避免在异常构造时传入
HttpStatus.BAD_REQUEST—— 那会让异常和 Web 层绑定,无法复用于 RPC 或消息消费场景
全局异常处理器怎么区分BizException和其他异常
Spring 的 @RestControllerAdvice 默认按异常类型匹配,BizException 是你唯一的业务异常基类,所以处理器方法签名必须精准锁定它,而不是笼统捕获 Exception。
容易忽略的坑:有人写一个 @ExceptionHandler(Exception.class) 处理所有异常,再在方法里用 instanceof 分支判断,结果 NullPointerException、IllegalArgumentException 全被当成业务异常返回 400,掩盖了真实 bug。
- 显式声明
@ExceptionHandler(BizException.class),专治业务拒绝 - 另配一个
@ExceptionHandler(RuntimeException.class)捕获未预期的运行时异常,返回 500 并打 ERROR 日志 - 对
HttpMessageNotReadableException这类 Spring 自带异常,单独写 handler 返回 400,别让它掉进通用RuntimeException分支 - 确保
BizException不被任何中间件(如 Sentinel、Dubbo)自动包装成其他异常类型,否则类型匹配失效
分层真正的难点不在代码怎么写,而在每次抛异常前,得想清楚:这是用户操作越界,还是系统某环崩了——前者归 BizException,后者连日志都要标 ERROR 级别。很多人卡在这一步,不是不会写,是没想明白语义边界。










