异常处理与统一响应
本教程共 48 篇 · 第 21 篇 · 更新于 2026-08-13 · 约 3 分钟阅读
本节目标:把散落在各处的 try-catch 收拢成全局异常处理,用 ProblemDetail 输出标准错误结构,让调用方拿到一致的报错格式。
不处理异常会怎样
接口里抛异常,Spring Boot 默认返回一个错误页或 JSON,长这样:
{
"timestamp": "2026-08-13T10:00:00",
"status": 404,
"error": "Not Found",
"path": "/products/999"
}
这是内置的 /error 兜底机制在干活。能用,但有两个问题:
- 字段是 Spring 定的,团队风格没法统一。
- 业务异常(「库存不足」「用户不存在」)没有专门映射,全变 500。
正经项目要自己接管异常响应。主角是 @RestControllerAdvice。
@ExceptionHandler:先学会局部处理
单个控制器里就能处理自己的异常。方法上加 @ExceptionHandler,指定处理哪种异常:
package com.example.demo.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.exception.NotFoundException;
@RestController
public class ProductController {
@GetMapping("/products/{id}")
public String get(@PathVariable Long id) {
if (!"1".equals(id)) {
throw new NotFoundException("商品不存在");
}
return "商品 1";
}
@ExceptionHandler(NotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handleNotFound(NotFoundException ex) {
return ex.getMessage();
}
}
局部处理只对本控制器生效。多个控制器都要用,就得复制粘贴,不合适。
@RestControllerAdvice:全局收拢
@RestControllerAdvice 是「控制器增强」。它扫描所有控制器,把 @ExceptionHandler 方法变成全局兜底。
先定义一个业务异常:
package com.example.demo.exception;
public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
}
}
再写全局处理器:
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 业务异常:404
@ExceptionHandler(NotFoundException.class)
public ProblemDetail handleNotFound(NotFoundException ex) {
ProblemDetail detail = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND, ex.getMessage());
detail.setTitle("资源不存在");
return detail;
}
// 参数校验失败:400
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
ProblemDetail detail = ProblemDetail.forStatusAndDetail(
HttpStatus.BAD_REQUEST, message);
detail.setTitle("参数校验失败");
return detail;
}
}
控制器只管抛异常,响应格式交给这里,各司其职。
ProblemDetail:4.x 推荐的标准错误结构
Spring Framework 6 起支持 RFC 9457 定义的 Problem Details。Spring Boot 4.x 沿用并推荐。它给错误响应定了一套标准字段:
{
"type": "about:blank",
"title": "资源不存在",
"status": 404,
"detail": "商品不存在",
"instance": "/products/999"
}
字段含义:
type:错误类型的文档链接,默认about:blank。title:人类可读的简短标题。status:HTTP 状态码。detail:具体错误描述。instance:出错的请求地址。
ProblemDetail 对象用构建方法创建,还能加自定义字段:
ProblemDetail detail = ProblemDetail.forStatusAndDetail(
HttpStatus.BAD_REQUEST, "库存不足");
detail.setProperty("stock", 0);
setProperty 的键值会平铺进 JSON 里。业务错误码、剩余库存这类信息放这里。
想让 Spring Boot 内置的错误兜底也输出 ProblemDetail 格式,开一个配置:
spring:
mvc:
problemdetails:
enabled: true
Note这个开关(spring.mvc.problemdetails.enabled)让框架自身的 404、405 等错误也走 RFC 9457 结构。3.0 起可用,4.x 默认关闭,按需开启。
对照:旧版自定义 Map 响应
很多老教程(含 2.x 素材)的做法是自定义响应体:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public Map<String, Object> handleNotFound(NotFoundException ex) {
Map<String, Object> body = new HashMap<>();
body.put("code", "000001");
body.put("message", ex.getMessage());
body.put("result", null);
return body;
}
}
自己定 code、message、result 结构。好处是灵活,坏处是每家各写各的,调用方得分别适配。
两个方案怎么选?新项目建议直接上 ProblemDetail,标准、省事。已有团队约定的旧系统,保留自定义结构也完全合理。
错误页兜底
非接口场景(浏览器直接访问、静态资源缺失)还要配错误页。在 src/main/resources/templates/error/ 下放模板,按状态码命名:
src/main/resources/
└── templates/
└── error/
├── 404.html # 404 专用
└── 5xx.html # 所有 5xx 通用
Spring Boot 会自动匹配。静态错误页放 static/error/ 下同样生效。
本节小结
- @ExceptionHandler 局部处理,@RestControllerAdvice 全局收拢。
- ProblemDetail 是 4.x 推荐的标准错误结构,实现 RFC 9457。
spring.mvc.problemdetails.enabled=true让内置错误也走标准格式。- 自定义 Map 响应是旧版常见做法,新项目优先 ProblemDetail。