Spring Boot 全局异常处理与参数校验功能实现

 更新时间:2026年07月07日 10:12:39   作者:豆瓣鸡  
在Spring Boot中,全局异常处理和参数校验是非常重要的功能,可以帮助提升应用的健壮性和用户体验,本文给大家介绍Spring Boot全局异常处理与参数校验,感兴趣的朋友跟随小编一起看看吧

全局异常处理与参数校验

概述

接口报错的时候,如果直接让异常往外抛,前端拿到的是一坨 Tomcat 默认的错误页面 HTML,或者一串乱七八糟的堆栈信息。前端没法判断"这是什么错、该提示用户什么"。

全局异常处理就是兜底:不管哪里出了什么错,最后返回给前端的始终是统一的 JSON 结构——有错误码、有错误信息,前端可以稳稳地拿这个做处理。

参数校验也一起搞了:Controller 里别写一堆 if (xxx == null),靠注解声明规则,校验不通过直接抛异常,由全局处理器统一拦下来返回。

引入依赖

@ControllerAdvice@ExceptionHandler 这些来自 spring-boot-starter-web,如果你已经用了 Web 模块就不用额外加了。

但是参数校验需要单独引入:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Spring Boot 2.x 时代 validation 内嵌在 spring-boot-starter-web 里,3.x 拆出来了。不加这个依赖的话,@NotBlank@Valid 不会报编译错,但运行时校验不生效——加了注解等于白加,所有参数直接放行。

第一步:搭异常体系

common

我们需要两个基础的东西:异常接口 + 异常类。放在 common 模块,全项目都能用。

BaseExceptionInterface

public interface BaseExceptionInterface {
    String getErrorCode();
    String getErrorMessage();
}

就是一个接口,规定"异常必须包含错误码和错误信息"。后面我们的枚举实现它,BizException 也依赖它。

BizException

BizException 代表业务异常,每个模块都可能出现业务异常,所以定义在 common 模块里面。

@Getter
@Setter
public class BizException extends RuntimeException {
    private String errorCode;
    private String errorMessage;
    public BizException(BaseExceptionInterface baseExceptionInterface) {
        this.errorCode = baseExceptionInterface.getErrorCode();
        this.errorMessage = baseExceptionInterface.getErrorMessage();
    }
}

继承 RuntimeException,这样在业务代码里抛的时候不用在方法签名上 throws,Spring 也默认对运行时异常做事务回滚。

构造参数只接收 BaseExceptionInterface,不让你随便 new BizException("随便写的错")——所有异常必须预先在枚举里定义好,方便统一管理。

具体业务模块

定义我这个模块会有什么异常

ResponseCodeEnum

@Getter
@AllArgsConstructor
public enum ResponseCodeEnum implements BaseExceptionInterface {
    // 通用异常
    SYSTEM_ERROR("AUTH-10000", "出错啦,后台小哥正在努力修复中..."),
    PARAM_NOT_VALID("AUTH-10001", "参数错误"),
    // 业务异常往后加
    ;
    private final String errorCode;
    private final String errorMessage;
}

这里其实很巧妙,这里是 implements BaseExceptionInterface,但是这个接口定义的是 getErrorCodegetErrorMessage 两个方法,我这里定义了两个变量,加上 @Getter 刚好重写了接口的两个方法。

要点说明
实现 BaseExceptionInterface枚举实例可以直接丢进 BizException 的构造参数
错误码前缀AUTH 代表这个模块,和其他模块的错误码区分开。比如订单模块用 ORDER-20000
后面加什么登录失败、用户不存在、权限不足……随着业务迭代,直接在枚举里加就行

抛一个业务异常就一句话

throw new BizException(ResponseCodeEnum.SYSTEM_ERROR);

三者关系

BizException 的构造参数是 BaseExceptionInterface(接口),不是 ResponseCodeEnum(具体枚举)。所以:

BizException 构造参数
        ↓ 依赖
BaseExceptionInterface(接口:规定必须有 errorCode + errorMessage)
        ↑ 实现
ResponseCodeEnum(枚举:集中管理所有错误码)
  • 接口 起约定作用:不管哪个模块、哪个枚举,只要你实现了这两个 getter,就能丢进 BizException
  • 枚举 起集中管理作用:所有错误码一处定义,不散落
  • BizException 只认接口不认枚举,auth 模块的 AuthResponseCodeEnum、order 模块的 OrderResponseCodeEnum,只要实现了接口,都能用

如果直接用 new BizException(String, String),错误码会散落在各个角落,改一个提示语得全项目搜,想看看系统里有哪些错误码也无从下手。

第二步:全局异常处理器

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /** 业务异常 */
    @ExceptionHandler({ BizException.class })
    @ResponseBody
    public Response<Object> handleBizException(HttpServletRequest request, BizException e) {
        log.warn("{} request fail, errorCode: {}, errorMessage: {}",
                request.getRequestURI(), e.getErrorCode(), e.getErrorMessage());
        return Response.fail(e);
    }
    /** 参数校验失败 */
    @ExceptionHandler({ MethodArgumentNotValidException.class })
    @ResponseBody
    public Response<Object> handleMethodArgumentNotValidException(
            HttpServletRequest request, MethodArgumentNotValidException e) {
        String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
        BindingResult bindingResult = e.getBindingResult();
        StringBuilder sb = new StringBuilder();
        Optional.ofNullable(bindingResult.getFieldErrors()).ifPresent(errors -> {
            errors.forEach(error ->
                sb.append(error.getField())
                  .append(" ")
                  .append(error.getDefaultMessage())
                  .append(", 当前值: '")
                  .append(error.getRejectedValue())
                  .append("'; ")
            );
        });
        log.warn("{} request error, errorCode: {}, errorMessage: {}",
                request.getRequestURI(), errorCode, sb.toString());
        return Response.fail(errorCode, sb.toString());
    }
    /** 其他未捕获的异常——兜底 */
    @ExceptionHandler({ Exception.class })
    @ResponseBody
    public Response<Object> handleOtherException(HttpServletRequest request, Exception e) {
        log.error("{} request error", request.getRequestURI(), e);
        return Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
    }
}

三个关键注解

注解干什么的
@ControllerAdvice全局增强,所有 Controller 的异常都会被这里的 @ExceptionHandler 拦截
@ExceptionHandler声明这个方法专门处理哪种类型的异常
@ResponseBody返回的内容直接序列化为 JSON,不要走视图模板

三层兜底结构

Exception(其他所有异常)         ← 第三层:保底,全拦住了
  └─ MethodArgumentNotValidException  ← 第二层:参数校验
  └─ BizException                      ← 第一层:业务异常

Spring 匹配规则是找最精确的。如果抛的是 BizException,只走第一个 handler;抛的是 MethodArgumentNotValidException,走第二个;其他任何没被前两个匹配到的异常,都掉进第三个兜底。三层覆盖,不会有漏网之鱼。

注意:BizExceptionlog.warn,因为它是业务"可预期"的错,不需要大惊小怪。最后那个 Exceptionlog.error 并打印了堆栈,因为这是真正该修 bug 的异常。

第三步:参数校验

上面那个 MethodArgumentNotValidException 是什么时候抛的?当加了 @Valid 的入参校验不通过时

在实体类属性上加校验注解

@Data
public class User {
    @NotBlank(message = "昵称不能为空")
    private String nickName;
    private LocalDateTime createTime;
}

在 Controller 方法参数里加 @Valid

@PostMapping("/test2")
@ApiOperationLog(description = "测试接口2")
public Response<User> test2(@Valid @RequestBody User user) {
    return Response.success(user);
}

注意 @Valid 是加在方法参数上的,不是加在类上。二者缺一不可:

  • 类上没有 @NotBlank → 不会校验,什么都过得去
  • 方法参数没有 @Valid → 不会触发校验,注解成了摆设

校验错误信息是怎么拼出来的?

GlobalExceptionHandler 里的 handleMethodArgumentNotValidException 遍历了 bindingResult.getFieldErrors(),把每条错误的字段名、校验描述、实际收到的值拼接在一起:

sb: "nickName 昵称不能为空, 当前值: ''; "

这样前端/开发人员一看就知道是哪个字段、为什么不通过、实际传了什么。

常用校验注解

注解校验规则
@NotBlank不能为 null 且不能为空字符串(""不行," "也不行)
@NotNull不能为 null(但 "" 可以)
@NotEmpty不能为 null 且不能为空集合/空字符串
@Email必须是邮箱格式
@Size(min, max)字符串/集合长度范围
@Min / @Max数字最小值/最大值
@Pattern(regexp)自定义正则校验

选哪个要看业务场景:比如昵称不允许空字符串,用 @NotBlank 而不是 @NotNull,因为 @NotNull 允许 "" 通过。

完整流程串联

以一个参数校验失败的请求为例,整个链路:

  1. 前端 POST /test2,body 是 { "nickName": "" }
  2. Spring 序列化 JSON → User 对象
  3. @Valid 触发校验 → @NotBlank 校验失败
  4. Spring 抛出 MethodArgumentNotValidException
  5. @ControllerAdvicehandleMethodArgumentNotValidException 拦住
  6. 遍历错误字段,拼出 "nickName 昵称不能为空, 当前值: ''; "
  7. 返回 Response.fail("AUTH-10001", "...") → JSON

前端从头到尾拿到的都是统一结构,一个 if (response.success) 就能判断是否正常。

如何使用业务异常

你想手动中断一个请求、返回错误信息给前端:

// 不满足条件,直接抛
if (user == null) {
    throw new BizException(ResponseCodeEnum.SYSTEM_ERROR);
}

ResponseCodeEnum 里加新的异常码:

// 通用异常
SYSTEM_ERROR("AUTH-10000", "出错啦,后台小哥正在努力修复中..."),
PARAM_NOT_VALID("AUTH-10001", "参数错误"),
// 业务异常
LOGIN_FAILED("AUTH-20001", "用户名或密码错误"),
USER_NOT_FOUND("AUTH-20002", "用户不存在"),
UNAUTHORIZED("AUTH-20003", "没有访问权限"),
;

然后:

throw new BizException(ResponseCodeEnum.LOGIN_FAILED);

到此这篇关于Spring Boot 全局异常处理与参数校验功能实现的文章就介绍到这了,更多相关Spring Boot 全局异常处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中如何将String转JSONObject

    Java中如何将String转JSONObject

    这篇文章主要介绍了Java中如何将String转JSONObject,String类型转JSONObject,下面有两种方式可以进行转换,本文结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • SpringDoc基本使用的方法示例

    SpringDoc基本使用的方法示例

    SpringDoc是基于Spring Boot的现代化API文档生成工具,下面就来介绍一下SpringDoc基本使用,具有一定的参考价值,感兴趣的可以了解一下
    2025-07-07
  • mybatis plus自动生成代码的示例代码

    mybatis plus自动生成代码的示例代码

    本文主要介绍了mybatis plus自动生成代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • Javaweb开发中通过Servlet生成验证码图片

    Javaweb开发中通过Servlet生成验证码图片

    这篇文章主要为大家详细介绍了Javaweb开发中通过Servlet生成验证码图片的相关资料,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • 在Web项目中手机短信验证码实现的全过程记录

    在Web项目中手机短信验证码实现的全过程记录

    这篇文章主要给大家介绍了关于在Web项目中实现短信验证码的全过程记录,文中通过示例代码介绍的非常详细,在文末跟大家提供了源码下载,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-12-12
  • Java SerialVersionUID作用详解

    Java SerialVersionUID作用详解

    这篇文章主要介绍了Java SerialVersionUID作用详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Java日常练习题,每天进步一点点(38)

    Java日常练习题,每天进步一点点(38)

    下面小编就为大家带来一篇Java基础的几道练习题(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望可以帮到你
    2021-07-07
  • Java多线程下载的实现方法

    Java多线程下载的实现方法

    复习多线程的时候,练习了下,顺便记录一下:
    2013-03-03
  • 解决Springboot项目报错:java:错误:不支持发行版本 17

    解决Springboot项目报错:java:错误:不支持发行版本 17

    这篇文章主要给大家介绍了关于解决Springboot项目报错:java:错误:不支持发行版本17的相关资料,这个错误意味着你的Spring Boot项目正在使用Java 17这个版本,但是你的项目中未配置正确的Java版本,需要的朋友可以参考下
    2023-08-08
  • SpringBoot项目获取统一前缀配置及获取非确定名称配置方法

    SpringBoot项目获取统一前缀配置及获取非确定名称配置方法

    在SpringBoot项目中,使用@ConfigurationProperties注解可获取统一前缀的配置,具体做法是创建配置类,使用prefix属性指定配置的前缀,本文给大家介绍SpringBoot项目获取统一前缀配置以及获取非确定名称配置方法,感兴趣的朋友跟随小编一起看看吧
    2024-09-09

最新评论