Skip to content
第 17 章 后端 ⏱ 8 分钟阅读

第 17 章:自定义异常与最佳实践 ​

学习目标 ​

  • 学会自定义业务异常
  • 掌握统一错误码设计
  • 理解企业级异常使用规范

一、为什么自定义异常? ​

Java 自带的异常太通用(NullPointerException、IllegalArgumentException),业务上没法区分"用户不存在"还是"密码错误"。

企业规范:业务异常继承 RuntimeException(非受检,免得满屏 throws)。

二、最简单的自定义异常 ​

java
public class BusinessException extends RuntimeException {

    private final int code;       // 错误码

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}

// 使用
throw new BusinessException(20001, "用户不存在");

三、企业级:统一错误码枚举 + 业务异常 ​

第 1 步:错误码枚举

java
public enum ErrorCode {
    SUCCESS(0, "成功"),

    PARAM_INVALID(10001, "参数校验失败"),
    UNAUTHORIZED(10401, "未登录"),
    FORBIDDEN(10403, "无权限"),

    USER_NOT_FOUND(20001, "用户不存在"),
    PASSWORD_ERROR(20003, "密码错误"),

    ORDER_NOT_FOUND(30001, "订单不存在"),

    SYSTEM_ERROR(50000, "系统繁忙");

    private final int code;
    private final String message;

    ErrorCode(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() { return code; }
    public String getMessage() { return message; }
}

命名规范:模块(2 位) + 类型(1 位) + 序号(2 位)。例如 20001 = 用户模块(20)+ 业务错误(0)+ 第 1 个(01)。

第 2 步:业务异常类

java
public class BusinessException extends RuntimeException {

    private final int code;

    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.code = errorCode.getCode();
    }

    public BusinessException(ErrorCode errorCode, String detail) {
        super(errorCode.getMessage() + ": " + detail);   // 补充上下文
        this.code = errorCode.getCode();
    }

    public int getCode() {
        return code;
    }
}

第 3 步:业务代码抛异常

java
public User findById(Long id) {
    return userRepository.findById(id)
        .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
}

public void login(String username, String password) {
    User user = findByUsername(username);
    if (user == null) {
        throw new BusinessException(ErrorCode.USER_NOT_FOUND);  // 抛业务异常
    }
    if (!passwordEncoder.matches(password, user.getPassword())) {
        throw new BusinessException(ErrorCode.PASSWORD_ERROR);
    }
    // ...
}

四、异常使用规范 ​

场景做法
参数校验失败抛 BusinessException(PARAM_INVALID)
资源不存在抛 BusinessException(XXX_NOT_FOUND)
系统异常(数据库/网络)让它自然抛出,日志记录,别用业务异常包
异常吞掉❌ 绝对禁止
catch 后只 log 不处理❌ 也要禁止
java
// ❌ 反例:吞掉异常
try {
    doSomething();
} catch (Exception e) {
    e.printStackTrace();   // 只打印就完事
}

// ✅ 正例:包装后抛出
try {
    doSomething();
} catch (IOException e) {
    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "文件读取失败");
    // 上层会拿到完整 cause 链,便于排查
}

⚠️ 坑 1:catch 后只 print 不抛,等于把炸弹埋了——上层不知道出错了,数据可能不一致。

⚠️ 坑 2:业务异常继承 RuntimeException(非受检),不要继承 Exception(受检),否则所有方法都得 throws,代码会很难看。

五、本章小结 ​

要点关键
业务异常继承 RuntimeException,带 code 字段
错误码枚举集中管理所有错误,IDE 可一键查看
何时抛参数校验、状态不符、资源不存在
何时不抛系统异常(数据库/网络)不要包装
catch 后必须处理重新抛出 / 包装抛出 / 业务兜底

动手练习 ​

  1. 错误码枚举:写 ErrorCode 枚举,至少 5 个错误
  2. 业务异常类:写 BusinessException 继承 RuntimeException
  3. 服务层异常:写 UserService.login(),密码错抛 BusinessException(PASSWORD_ERROR)

下一章:第 18 章:泛型 →

本站基于 VitePress 构建 · 由 StackHub 团队维护