业务校验失败应抛出语义明确的自定义RuntimeException子类,如InsufficientBalanceException;校验逻辑置于Service层开头,查完即断言,按优先级顺序执行,早失败早响应。

业务校验失败时,应抛出语义明确、可被捕获处理的自定义运行时异常(RuntimeException子类),避免用 Exception 或原生异常(如 IllegalArgumentException)掩盖业务意图。
定义清晰的业务异常类
为不同校验场景创建独立异常类,命名体现业务含义,比如:
-
InsufficientBalanceException—— 余额不足 -
InvalidOrderStatusException—— 订单状态不支持当前操作 -
PhoneNumberAlreadyUsedException—— 手机号已被注册
所有异常继承自 RuntimeException,无需强制 try-catch,符合“业务错误非意外”的设计原则。构造函数建议支持带错误码、消息和原始异常的重载:
public class InsufficientBalanceException extends RuntimeException {
private final String errorCode = "BALANCE_INSUFFICIENT";
private final BigDecimal requiredAmount;
public InsufficientBalanceException(BigDecimal requiredAmount) {
super("账户余额不足,需 " + requiredAmount);
this.requiredAmount = requiredAmount;
}
// 可选:提供 errorCode 和 message 分离的构造器,便于统一日志/返回
}
在 Service 层集中校验并抛出
校验逻辑应放在 Service 方法开头,失败即抛异常,不继续执行后续业务:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
立即学习“Java免费学习笔记(深入)”;
- 避免“先查再判断再抛”,而是“查完立刻断言”
- 不要用 if-else 返回布尔值或错误码,破坏控制流清晰性
- 多个校验项按业务优先级顺序排列,早失败早响应
示例:
public void transfer(String from, String to, BigDecimal amount) {
Account fromAccount = accountMapper.selectByNo(from);
if (fromAccount == null) {
throw new AccountNotFoundException(from);
}
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException(amount);
}
Account toAccount = accountMapper.selectByNo(to);
if (toAccount == null) {
throw new AccountNotFoundException(to);
}
// ... 执行转账
}
统一异常处理与响应封装
用 @ControllerAdvice 捕获自定义异常,转为标准 API 响应格式(如 { "code": "BALANCE_INSUFFICIENT", "message": "账户余额不足..." }):
- 区分业务异常和系统异常(如
NullPointerException),后者应记录堆栈并返回通用错误码 - 业务异常通常不打印完整堆栈,避免暴露内部细节
- 可结合
@ResponseStatus设置 HTTP 状态码(如 400)
示例:
@ExceptionHandler(InsufficientBalanceException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ApiResponse handleInsufficientBalance(InsufficientBalanceException e) {
return ApiResponse.fail(e.getErrorCode(), e.getMessage());
}
避免常见误区
- 不把校验异常命名为
ValidateException这类泛化名称,失去业务上下文 - 不在 Controller 层做核心校验,否则 Service 复用时逻辑分散
- 不滥用
throws Exception声明,会污染方法签名且无法精准捕获 - 不将校验异常用于流程控制(如循环中频繁抛/捕),影响性能和可读性

















