Spring Boot 全局异常处理实战:别再让前端看到 500 错误了!
作者:不想打工的码农
标签:#SpringBoot #Java #异常处理 #后端开发 #RESTful
大家好,我是“不想打工的码农”。
上周上线一个新功能,测试时一切正常,结果第二天客户反馈:“点保存按钮没反应!”
我一查日志,发现是数据库字段长度超限,抛了个 DataIntegrityViolationException,但前端只收到一个空白的 500 响应。
这让我意识到:很多开发者只关注“正常流程”,却忽略了“异常流程”的用户体验。
今天,我就用一个真实项目案例,教你怎么用 Spring Boot 的 @ControllerAdvice 做一套专业、统一、可维护的全局异常处理机制。
一、先看错误示范:到处 try-catch
很多初学者会这样写:
@PostMapping("/student")
public ResponseEntity<?> saveStudent(@RequestBody Student student) {
try {
studentService.save(student);
return ResponseEntity.ok("保存成功");
} catch (Exception e) {
log.error("保存学生失败", e);
return ResponseEntity.status(500).body("系统错误");
}
}
问题很明显:
- 每个接口都要写 try-catch,代码重复
- 错误信息太笼统(“系统错误”),前端无法提示用户
- 无法区分不同异常类型(比如参数校验失败 vs 数据库异常)
二、正确做法:用 @ControllerAdvice 统一处理
第一步:定义统一返回格式
先约定前后端的数据结构,避免各写各的:
public class Result<T> {
private int code; // 200=成功,400=参数错误,500=系统错误
private String msg; // 错误描述
private T data; // 成功时返回的数据
// 构造方法
public static <T> Result<T> success(T data) {
return new Result<>(200, "success", data);
}
public static <T> Result<T> error(int code, String msg) {
return new Result<>(code, msg, null);
}
// getter/setter 省略(实际项目必须有)
}
第二步:创建全局异常处理器
新建 GlobalExceptionHandler.java:
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.validation.BindException;
import javax.validation.ConstraintViolationException;
@RestControllerAdvice // = @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 处理参数校验失败(@Valid 注解触发的)
@ExceptionHandler(BindException.class)
public Result<?> handleBindException(BindException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.error(400, "参数错误:" + msg);
}
// 处理 JSR-303 校验失败(@Validated + 方法参数)
@ExceptionHandler(ConstraintViolationException.class)
public Result<?> handleConstraintViolation(ConstraintViolationException e) {
String msg = e.getConstraintViolations().iterator().next().getMessage();
return Result.error(400, "参数错误:" + msg);
}
// 处理数据库约束异常(如唯一索引冲突、字段超长)
@ExceptionHandler(DataIntegrityViolationException.class)
public Result<?> handleDbException(DataIntegrityViolationException e) {
log.error("数据库操作异常", e);
if (e.getMessage().contains("Duplicate entry")) {
return Result.error(400, "该学号已存在,请修改后重试");
}
if (e.getMessage().contains("Data too long")) {
return Result.error(400, "输入内容过长,请检查后提交");
}
return Result.error(500, "数据保存失败");
}
// 处理所有未捕获的异常(兜底)
@ExceptionHandler(Exception.class)
public Result<?> handleGenericException(Exception e) {
log.error("系统未知异常", e);
return Result.error(500, "系统繁忙,请稍后再试");
}
}
✅ 关键点说明:
@RestControllerAdvice会自动把返回对象转成 JSON- 按异常类型分层处理,越具体的异常放前面
- 所有异常都记录日志,便于排查
- 返回给前端的信息要具体但不暴露敏感信息(比如不说“主键冲突”,而说“学号已存在”)
第三步:在 Controller 中只写正常逻辑
现在你的接口可以清爽多了:
@PostMapping("/student")
public Result<String> saveStudent(@Valid @RequestBody Student student) {
studentService.save(student);
return Result.success("保存成功");
}
注意:这里加了 @Valid,当参数不符合校验规则时,会自动抛出 BindException,被全局处理器捕获。
三、验证效果
- 正常请求:json
{ "code": 200, "msg": "success", "data": "保存成功" } - 参数校验失败(比如姓名为空):json
{ "code": 400, "msg": "参数错误:姓名不能为空", "data": null } - 数据库唯一索引冲突:json
{ "code": 400, "msg": "该学号已存在,请修改后重试", "data": null } - 其他未知异常:json
{ "code": 500, "msg": "系统繁忙,请稍后再试", "data": null }
前端拿到这样的响应,就能友好地提示用户,而不是显示“网络错误”或白屏。
四、常见问题
Q1:为什么我的 BindException 没被捕获?
A:检查 Controller 方法参数是否加了 @Valid,且实体类字段有 @NotBlank 等注解。
Q2:自定义业务异常怎么处理?
A:可以定义自己的异常类,比如:
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
}
然后在 GlobalExceptionHandler 中加一个 @ExceptionHandler(BusinessException.class)。
Q3:需要对每个模块返回不同的错误码吗?
A:建议统一用 HTTP 状态码语义(4xx=客户端错,5xx=服务端错),业务错误码可通过 msg 或额外字段传递,避免过度设计。
五、总结
一套好的异常处理机制,应该做到:
- ✅ 对用户友好:错误信息清晰可操作
- ✅ 对开发者友好:日志完整,便于定位
- ✅ 对代码友好:无需到处写 try-catch
记住:用户不关心你用了什么框架,只关心“为什么不能用”。
把异常处理做好,你的接口才算真正“上线可用”。
我是“不想打工的码农”,一个坚持写能跑通、能上线代码的普通开发者。
如果你觉得有用,欢迎点赞、收藏、关注!
更多推荐


所有评论(0)