首页 / Spring Boot 入门教程 / 异常处理与统一响应

Spring Boot 入门教程

异常处理与统一响应

本教程共 48 篇 · 第 21 篇 · 更新于 2026-08-13 · 约 3 分钟阅读

Spring Boot异常处理RestControllerAdviceProblemDetail统一响应RFC 9457

本节目标:把散落在各处的 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;
    }
}

自己定 codemessageresult 结构。好处是灵活,坏处是每家各写各的,调用方得分别适配。

两个方案怎么选?新项目建议直接上 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。