需要自定义业务异常,因为Java默认异常无法准确表达“用户余额不足”等业务语义,易混淆bug与合理拦截;应分层定义领域异常、应用异常、接口异常,并统一继承RuntimeException、提供多构造器、使用规范错误码。

为什么需要自定义业务异常
Java 默认的 Exception 和 RuntimeException 无法准确表达业务语义。比如“用户余额不足”“订单已取消”“手机号已被注册”,这些不是系统故障,而是明确的业务规则拦截。直接抛 IllegalArgumentException 或 RuntimeException 会让调用方难以区分是程序 bug 还是合理业务拒绝,也难做针对性处理(如前端提示、重试控制、日志分级)。自定义业务异常的核心目的是:**让异常携带业务上下文、可识别、可捕获、可响应**。
分层设计:按职责划分异常类型
业务异常不应只有一类,而应按分层职责划分为三类,避免“一异常走天下”:
-
领域异常(DomainException):定义在 domain 层,封装核心业务规则失败。例如
InsufficientBalanceException、InvalidOrderStatusException。它不关心 HTTP 或 RPC,只回答“这里业务逻辑为何不成立”。构造时建议包含错误码(如BALANCE_001)和必要参数(如当前余额、所需金额)。 -
应用异常(ApplicationException):位于 application 层,用于封装用例执行中的非技术性失败。例如
OrderCreationFailedException,可能聚合多个领域异常,或表示“创建订单流程因校验未通过而终止”。它更贴近用户操作意图,适合被接口层捕获并转为统一响应。 -
接口异常(ApiException):位于 web / controller 层,专为对外暴露设计。继承
RuntimeException,通常带errorCode、message、httpStatus。例如UserAlreadyExistsApiException对应 409 Conflict。它是前端真正“看得懂”的异常,不暴露内部实现细节。
如何规范定义与使用
定义一个干净可用的业务异常,需兼顾可读性、可扩展性和框架兼容性:
- 所有自定义异常都继承
RuntimeException(除非你明确需要强制 try-catch); - 每个异常类提供至少两个构造器:一个仅传 message,一个支持 message + cause,一个支持 message + code + args(便于国际化和占位符填充);
- 错误码建议采用「模块_场景_序号」格式(如
USER_REGISTER_001),全局唯一且可检索; - 避免在 service 方法签名中声明 throws 自定义业务异常(违背“业务异常非意外”原则),但可在 javadoc 中注明可能抛出的异常类型;
- 统一用 @ControllerAdvice + @ExceptionHandler 拦截 ApiException 及其子类,返回标准 JSON 响应体(含 code、msg、timestamp 等)。
一个轻量实用的基类示例
可以定义一个抽象基类简化开发:
立即学习“Java免费学习笔记(深入)”;
public abstract class BusinessException extends RuntimeException {
private final String errorCode;
private final Object[] args;
public BusinessException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
this.args = new Object[0];
}
public BusinessException(String errorCode, String message, Object... args) {
super(String.format(message, args));
this.errorCode = errorCode;
this.args = args;
}
// getter 省略...
}
然后具体异常只需继承它:public class UserAlreadyExistsException extends BusinessException { ... }。后续配合消息资源文件或枚举管理提示文案,即可支撑多语言和动态提示。










