恋上蓝花楹

Spring Boot 优雅异常处理实战:让错误返回值更规范

在日常开发中,我们经常会遇到各种异常情况:用户请求参数不合法、数据库操作失败、业务逻辑校验未通过……如果这些错误都直接抛出500异常,不仅用户体验极差,还会让前端开发者无从下手。今天,我就来分享一套在Spring Boot中优雅处理异常的实战方案。

一、传统异常处理的痛点

很多小伙伴在Controller里是这样写异常处理的:

@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    if (id == null || id <= 0) {
        throw new RuntimeException("参数错误");
    }
    return userService.findById(id);
}

这样做的问题显而易见:

  • 返回的错误格式不统一,前端难以解析
  • 异常信息暴露在堆栈中,不安全
  • 每个接口都要重复编写异常处理逻辑
  • 无法区分业务异常和系统异常

二、构建统一的响应结构

首先,我们需要定义一个统一的返回值结构。建议包含以下字段:

public class ApiResponse<T> {
    private int code;        // 业务状态码
    private String message;  // 提示信息
    private T data;         // 返回数据
    private long timestamp; // 时间戳

    // 成功响应
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "success", data);
    }

    // 失败响应
    public static ApiResponse<?> error(int code, String message) {
        return new ApiResponse<>(code, message, null);
    }
}

三、自定义异常 + 全局异常处理器

第一步:定义业务异常类

public class BusinessException extends RuntimeException {
    private int code = 400;

    public BusinessException(String message) {
        super(message);
    }

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

    public int getCode() {
        return code;
    }
}

第二步:创建全局异常处理器

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ApiResponse<?> handleBusinessException(BusinessException e) {
        return ApiResponse.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<?> handleValidationException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return ApiResponse.error(400, message);
    }

    @ExceptionHandler(Exception.class)
    public ApiResponse<?> handleException(Exception e) {
        // 生产环境建议记录日志,不暴露具体异常信息
        return ApiResponse.error(500, "系统繁忙,请稍后再试");
    }
}

四、实战效果

使用上述方案后,前端收到的响应变成了统一的格式:

// 成功响应
{
    "code": 200,
    "message": "success",
    "data": { "id": 1, "name": "张三" },
    "timestamp": 1678892341000
}

// 参数错误
{
    "code": 400,
    "message": "用户ID不能为空",
    "data": null,
    "timestamp": 1678892341000
}

// 系统异常
{
    "code": 500,
    "message": "系统繁忙,请稍后再试",
    "data": null,
    "timestamp": 1678892341000
}

五、进阶:使用枚举定义错误码

为了更好地管理错误码,建议创建错误码枚举:

public enum ErrorCode {
    USER_NOT_FOUND(1001, "用户不存在"),
    INVALID_PARAMETER(1002, "参数不合法"),
    AUTH_FAILED(1003, "认证失败"),
    // ... 更多错误码
    ;

    private final int code;
    private final String message;

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

    public BusinessException exception() {
        return new BusinessException(code, message);
    }
}

使用时只需要:

throw ErrorCode.USER_NOT_FOUND.exception();

总结

通过以上方案,我们可以实现:

  • 统一响应格式 - 前后端约定一套标准,沟通更高效
  • 集中异常处理 - 一次配置,全局生效
  • 精细的错误码管理 - 便于问题定位和统计分析
  • 安全的错误信息 - 不暴露系统内部细节

如果你还在为异常处理而烦恼,不妨试试这套方案。代码优雅了,维护成本自然就降下来了。


如果你觉得这篇文章对你有帮助,欢迎点赞、收藏!如果有问题,也欢迎在评论区留言讨论。

wulilele

我是一名热爱科技与AI的软件工程师。