4. 异常类层次结构设计:基础异常类、业务异常类、系统异常类、自定义异常类的继承体系
在企业级系统中,异常类的层次结构设计是错误处理骨架的基石。一个设计良好的异常继承体系,能够帮助开发团队快速定位问题根因、统一错误响应格式、降低跨模块耦合。本章将深入剖析四层异常体系的设计原理与工程落地细节。
4.1 四层异常体系总览
我们采用经典的“基础 → 业务 → 系统 → 自定义”四层继承模型。每一层承担不同的职责,从最顶层的通用契约到底层的具体场景实现,形成完整的错误分类树。
4.2 基础异常类(BaseException)
BaseException 是整个异常体系的根节点,通常继承自 RuntimeException(非受检异常)。它定义了所有异常必须携带的通用契约:错误码、错误消息、时间戳、异常级别。
设计原则:基础异常类不包含任何业务语义,只提供最底层的属性容器和构造方法。所有子类必须通过 super() 调用父类构造器。
public abstract class BaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final String errorCode; // 错误码,如 "BIZ-0001"
private final String errorMessage; // 错误描述
private final long timestamp; // 异常发生时间戳
private final ErrorLevel level; // 异常级别:WARN / ERROR / FATAL
// 核心构造器
public BaseException(String errorCode, String errorMessage, ErrorLevel level) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.timestamp = System.currentTimeMillis();
this.level = level;
}
// 支持原始异常的构造器
public BaseException(String errorCode, String errorMessage, ErrorLevel level, Throwable cause) {
super(errorMessage, cause);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.timestamp = System.currentTimeMillis();
this.level = level;
}
// getter 方法
public String getErrorCode() { return errorCode; }
public String getErrorMessage() { return errorMessage; }
public long getTimestamp() { return timestamp; }
public ErrorLevel getLevel() { return level; }
}
4.3 业务异常类(BusinessException)
BusinessException 继承自 BaseException,代表可预知的业务规则违反。例如:库存不足、订单状态不允许取消、用户余额不足等。这类异常通常需要在前端展示友好的提示信息,并触发特定的业务补偿流程。
| 属性 | 说明 | 示例值 |
|---|---|---|
| errorCode | 业务错误码,前缀 BIZ | BIZ-1001 |
| errorMessage | 面向用户的友好提示 | “商品库存不足,当前仅剩 2 件” |
| level | 通常为 WARN | ErrorLevel.WARN |
| cause | 可选,业务异常一般不包装底层技术异常 | null |
public class BusinessException extends BaseException {
public BusinessException(String errorCode, String errorMessage) {
super(errorCode, errorMessage, ErrorLevel.WARN);
}
public BusinessException(String errorCode, String errorMessage, Throwable cause) {
super(errorCode, errorMessage, ErrorLevel.WARN, cause);
}
}
工程建议:业务异常不要包装底层 SQL 或网络异常,否则会暴露技术细节。如果业务操作因技术故障失败,应抛出 SystemException 而非 BusinessException。
4.4 系统异常类(SystemException)
SystemException 同样继承自 BaseException,代表不可预知的技术故障。例如:数据库连接池耗尽、Redis 超时、磁盘 I/O 错误、第三方服务不可用。这类异常通常需要触发告警、记录完整堆栈、并执行降级或熔断策略。
| 属性 | 说明 | 示例值 |
|---|---|---|
| errorCode | 系统错误码,前缀 SYS | SYS-2001 |
| errorMessage | 面向运维的技术描述 | “数据库连接池已满,等待超时 30s” |
| level | 通常为 ERROR 或 FATAL | ErrorLevel.ERROR |
| cause | 必须包装原始技术异常 | new SQLException(...) |
public class SystemException extends BaseException {
public SystemException(String errorCode, String errorMessage, Throwable cause) {
super(errorCode, errorMessage, ErrorLevel.ERROR, cause);
}
public SystemException(String errorCode, String errorMessage, ErrorLevel level, Throwable cause) {
super(errorCode, errorMessage, level, cause);
}
}
关键区别:BusinessException 的 level 为 WARN,不触发告警;SystemException 的 level 为 ERROR 或 FATAL,必须触发告警并记录全堆栈。这是区分“业务提示”和“系统故障”的核心分界线。
4.5 自定义异常类(Custom Exception)
自定义异常类位于继承体系的第三层和第四层,是实际项目中使用频率最高的异常类型。它们继承自 BusinessException 或 SystemException,为特定业务模块或技术组件提供精确的错误语义。
4.5.1 自定义业务异常示例
// 订单模块业务异常
public class OrderBizException extends BusinessException {
public static final String INSUFFICIENT_STOCK = "BIZ-1001";
public static final String ORDER_STATUS_INVALID = "BIZ-1002";
public static final String PAYMENT_TIMEOUT = "BIZ-1003";
public OrderBizException(String errorCode, String errorMessage) {
super(errorCode, errorMessage);
}
// 静态工厂方法,简化调用
public static OrderBizException insufficientStock(String skuId, int available) {
return new OrderBizException(INSUFFICIENT_STOCK,
String.format("商品 [%s] 库存不足,当前可用库存: %d", skuId, available));
}
}
// 用户模块业务异常
public class UserBizException extends BusinessException {
public static final String USER_NOT_FOUND = "BIZ-2001";
public static final String PASSWORD_ERROR = "BIZ-2002";
public static final String ACCOUNT_LOCKED = "BIZ-2003";
public UserBizException(String errorCode, String errorMessage) {
super(errorCode, errorMessage);
}
}
4.5.2 自定义系统异常示例
// 数据库系统异常
public class DbSysException extends SystemException {
public static final String CONNECTION_TIMEOUT = "SYS-1001";
public static final String DEADLOCK_DETECTED = "SYS-1002";
public static final String QUERY_TIMEOUT = "SYS-1003";
public DbSysException(String errorCode, String errorMessage, Throwable cause) {
super(errorCode, errorMessage, cause);
}
}
// 缓存系统异常
public class CacheSysException extends SystemException {
public static final String REDIS_UNAVAILABLE = "SYS-2001";
public static final String CACHE_KEY_NOT_FOUND = "SYS-2002";
public CacheSysException(String errorCode, String errorMessage, Throwable cause) {
super(errorCode, errorMessage, cause);
}
}
4.6 异常级别枚举(ErrorLevel)
异常级别是异常体系中的关键元数据,决定了异常被捕获后的处理策略。我们定义三个级别:
| 级别 | 枚举值 | 处理策略 | 典型场景 |
|---|---|---|---|
| 警告 | WARN | 返回友好提示,不告警,不记录堆栈 | 参数校验失败、库存不足 |
| 错误 | ERROR | 记录完整堆栈,触发告警,执行降级 | 数据库超时、第三方服务不可用 |
| 致命 | FATAL | 记录堆栈,触发紧急告警,可能重启服务 | 磁盘空间满、OOM、配置中心不可用 |
public enum ErrorLevel {
WARN(1, "警告"),
ERROR(2, "错误"),
FATAL(3, "致命");
private final int code;
private final String description;
ErrorLevel(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() { return code; }
public String getDescription() { return description; }
}
4.7 异常类设计最佳实践
核心原则:异常类层次结构的设计目标是“精确分类 + 统一契约”,而不是“无限细分”。每增加一个自定义异常类,都应该有明确的业务或技术语义区分。
- 原则一:基础异常类必须抽象,禁止直接实例化 BaseException。所有异常必须通过子类抛出。
- 原则二:业务异常和系统异常严格分离。业务异常不包装技术异常,系统异常必须包装原始异常。
- 原则三:自定义异常类使用静态工厂方法代替多参数构造器,提高代码可读性。
- 原则四:错误码采用统一前缀(BIZ / SYS)加数字编号,便于日志检索和监控告警。
- 原则五:异常类必须实现 serialVersionUID,避免跨 JVM 反序列化失败。
4.8 异常类使用示例
// 业务层抛出业务异常
public void deductStock(String skuId, int quantity) {
int available = stockRepository.getAvailable(skuId);
if (available < quantity) {
throw OrderBizException.insufficientStock(skuId, available);
}
// 正常扣减逻辑...
}
// 基础设施层抛出系统异常
public Connection getConnection() {
try {
return dataSource.getConnection(30, TimeUnit.SECONDS);
} catch (SQLTimeoutException e) {
throw new DbSysException(DbSysException.CONNECTION_TIMEOUT,
"数据库连接获取超时,等待 30s 无响应", e);
}
}
// 全局异常处理器统一捕获
@ExceptionHandler(BaseException.class)
public ResponseEntity<ErrorResponse> handleBaseException(BaseException ex) {
ErrorResponse response = new ErrorResponse(
ex.getErrorCode(),
ex.getErrorMessage(),
ex.getTimestamp()
);
HttpStatus status = (ex instanceof SystemException)
? HttpStatus.INTERNAL_SERVER_ERROR
: HttpStatus.BAD_REQUEST;
return ResponseEntity.status(status).body(response);
}
工程落地检查清单:
- ✓ 所有异常类是否继承自 BaseException?
- ✓ 业务异常是否避免了包装技术异常?
- ✓ 系统异常是否都包装了原始 cause?
- ✓ 错误码是否遵循了统一前缀规范?
- ✓ 全局异常处理器是否区分了 WARN 和 ERROR 级别?