在日常开发中,我们经常会遇到各种异常情况:用户请求参数不合法、数据库操作失败、业务逻辑校验未通过……如果这些错误都直接抛出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();
总结
通过以上方案,我们可以实现:
- 统一响应格式 - 前后端约定一套标准,沟通更高效
- 集中异常处理 - 一次配置,全局生效
- 精细的错误码管理 - 便于问题定位和统计分析
- 安全的错误信息 - 不暴露系统内部细节
如果你还在为异常处理而烦恼,不妨试试这套方案。代码优雅了,维护成本自然就降下来了。
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏!如果有问题,也欢迎在评论区留言讨论。
觉得有用就点个赞吧~