前言
在日常开发中,你一定见过这样的代码:
go
try {
// 业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
或者接口返回五花八门:
-
有的返回
500错误 -
有的返回一段文字
-
有的直接抛异常前端看不懂
-
每个接口都写一遍 try-catch
代码冗余、维护困难、前端对接痛苦、日志混乱,线上出问题还难定位。
统一异常处理,就是用来解决这些问题的。
今天这篇文章,带你从零到一实现 Spring Boot 全局统一异常处理,
包含:自定义异常、统一返回、全局捕获、异常分类、日志规范、实战案例。
看完直接落地到你的项目里。
一、为什么要做统一异常处理?
1. 现状痛点
-
每个接口都写 try-catch,代码重复
-
异常信息不规范,前端无法统一解析
-
错误信息暴露给用户,不安全
-
日志混乱,难以排查问题
-
系统稳定性差,容易直接崩接口
2. 统一异常处理的好处
-
代码更优雅,业务层不用处理异常
-
所有异常统一格式返回
-
异常可监控、可统计、可告警
-
安全:不把系统异常暴露给前端
-
提升开发效率与维护性
二、实现统一异常处理的核心组件
Spring Boot 提供两个最核心注解:
-
@RestControllerAdvice:全局捕获控制器异常 -
@ExceptionHandler:捕获指定类型异常
配合:
-
统一返回结果类
-
自定义业务异常
-
异常分类
-
日志规范
就能完成一套企业级异常体系。
三、第一步:定义统一返回格式
前端最需要的是固定结构。
创建 Result.java:
go
import lombok.Data;
@Data
public class Result<T> {
private int code;
private String msg;
private T data;
// 成功
public static <T> Result<T> success(T data) {
Result<T> r = new Result<>();
r.setCode(200);
r.setMsg("success");
r.setData(data);
return r;
}
// 失败
public static <T> Result<T> fail(int code, String msg) {
Result<T> r = new Result<>();
r.setCode(code);
r.setMsg(msg);
r.setData(null);
return r;
}
}
以后所有接口统一返回 Result。
四、第二步:自定义业务异常
系统异常和业务异常要分开。
创建 BusinessException.java:
go
public class BusinessException extends RuntimeException {
private int code;
private String msg;
public BusinessException(String msg) {
super(msg);
this.code = 500;
this.msg = msg;
}
public BusinessException(int code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
// getter
}
使用示例:
go
if (user == null) {
throw new BusinessException(400, "用户不存在");
}
五、第三步:定义全局异常捕获类(核心)
创建 GlobalExceptionHandler.java:
go
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 捕获业务异常
*/
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusinessException(BusinessException e) {
log.warn("业务异常:{}", e.getMsg());
return Result.fail(e.getCode(), e.getMsg());
}
/**
* 捕获参数校验异常
*/
@ExceptionHandler(IllegalArgumentException.class)
public Result<?> handleIllegalArgument(IllegalArgumentException e) {
log.warn("参数异常:{}", e.getMessage());
return Result.fail(400, e.getMessage());
}
/**
* 捕获空指针
*/
@ExceptionHandler(NullPointerException.class)
public Result<?> handleNullPointer(NullPointerException e) {
log.error("空指针异常", e);
return Result.fail(500, "服务器内部错误");
}
/**
* 兜底:所有其他异常
*/
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
log.error("系统异常", e);
return Result.fail(500, "服务器繁忙,请稍后再试");
}
}
作用:
所有 Controller 抛出的异常,都会进入这里统一处理。
六、第四步:整合 Validation 参数校验(非常实用)
前端传参错误,统一返回。
1. 引入依赖
go
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. DTO 加校验
go
import jakarta.validation.constraints.NotBlank;
public class UserLoginDTO {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
3. 接口加 @Valid
go
@PostMapping("/login")
public Result<?> login(@Valid @RequestBody UserLoginDTO dto) {
return Result.success("login success");
}
4. 捕获校验异常
go
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
@ExceptionHandler(BindException.class)
public Result<?> handleBindException(BindException e) {
FieldError fieldError = e.getFieldError();
String msg = fieldError.getDefaultMessage();
log.warn("参数校验失败:{}", msg);
return Result.fail(400, msg);
}
效果:
前端收到干净的:
go
{
"code": 400,
"msg": "用户名不能为空",
"data": null
}
七、第五步:规范日志级别(线上必备)
-
业务异常:warn
-
参数异常:warn
-
系统异常:error
示例:
go
log.warn("业务异常:{}", e.getMsg());
log.error("空指针异常", e);
便于日志平台筛选、告警。
八、第六步:实际业务中怎么用?(最真实示例)
Service 层:
go
@Service
public class UserService {
public User login(String username, String password) {
User user = userMapper.selectByUsername(username);
if (user == null) {
throw new BusinessException(400, "用户名或密码错误");
}
if (!user.getPassword().equals(password)) {
throw new BusinessException(400, "用户名或密码错误");
}
return user;
}
}
Controller 层:
go
@PostMapping("/login")
public Result<User> login(@Valid @RequestBody UserLoginDTO dto) {
User user = userService.login(dto.getUsername(), dto.getPassword());
return Result.success(user);
}
没有 try-catch!
代码极度清爽。
九、企业级异常分类规范
你可以直接在项目里使用这套异常码:
-
200 成功
-
400 参数错误
-
401 未登录
-
403 无权限
-
404 不存在
-
500 系统错误
-
1001~1999 业务自定义
示例:
go
throw new BusinessException(401, "请先登录");
throw new BusinessException(403, "无此权限");
throw new BusinessException(1001, "余额不足");