当前位置:

首页 > 编程开发 > SpringBoot全局异常处理与参数校验功能实现

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

通过@ControllerAdvice和@ExceptionHandler构建全局异常处理器,统一返回含错误码和信息的JSON,确保接口错误响应一致。定义BaseExceptionInterface接口和BizException异常类,使用枚举集中管理错误码,方便维护。参数校验依赖@Valid及校验注解,需引入spring-boot-starter-vali

后端接口报错,要是直接让异常往外抛,前端收到的要么是一大段 Tomcat 默认的 HTML 错误页,要么是乱七八糟的堆栈信息。前端同学一看就懵了——这到底是啥错?该提示用户什么?

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

所以得有一层兜底:不管哪层出了什么岔子,返回给前端的东西始终是统一格式的 JSON——有错误码、有错误信息,前端拿到手就能稳稳地做判断和处理。

参数校验也得一起安排上。Controller 里别写一堆 if (xxx == null),用注解声明规则,校验不通过直接抛异常,再由全局处理器统一拦截、统一返回。代码干净,逻辑也清晰。

引入依赖

@ControllerAdvice 和 @ExceptionHandler 这些都在 spring-boot-starter-web 里,如果你项目已经用了 Web 模块,这部分就不用重复加了。

但参数校验的依赖需要单独拎出来:


    org.springframework.boot
    spring-boot-starter-validation

注意一下版本差异: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;
}

这里有个小巧妙:BaseExceptionInterface 定义的 getErrorCode 和 getErrorMessage 两个方法,枚举里定义了两个变量,加上 @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 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 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 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,走第二个;其他任何没被前两个匹配到的异常,都掉进第三个兜底。三层覆盖,不会有漏网之鱼。

注意看日志级别:BizException 用的 log.warn,因为它是业务"可预期"的错,不需要大惊小怪。最后那个 Exception 用 log.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 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. @ControllerAdvice 中的 handleMethodArgumentNotValidException 将其拦截
  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);

所有异常码统一管理、统一抛出、统一处理,整个异常体系就非常规整了。

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
作者最新文章
编程开发
相关文章 更多
windsurf ide download Windows版安装教程
windsurf ide download Windows版安装教程

详解 Windsurf IDE 在 Windows 系统的下载来源、安装步骤及首次启动界面。指导用户如何导入编辑器设置、打开项目文件夹及使用终端与 AI 功能,适合初次接触该工具的开发者阅读。

ServBay安装配置详细教程与操作指南
ServBay安装配置详细教程与操作指南

新手入门 ServBay 本地开发环境,详解安装包下载、Dashboard 状态监控、Packages 组件安装、Services 服务控制及 Websites 项目配置。掌握 .servbay.config 版本管理与日志排查技巧,快速搭建稳定的 PHP、Node.js 等多语言开发环境。

codekit环境配置指南从安装到环境搭建完整教程
codekit环境配置指南从安装到环境搭建完整教程

详解 CodeKit 在 macOS 下的安装步骤、项目导入方法、Sass与JavaScript编译设置及浏览器自动刷新功能,助您快速搭建高效的前端开发环境。

codex安装windows 命令行完整操作教程
codex安装windows 命令行完整操作教程

详解Windows环境下安装OpenAI Codex CLI的步骤,包括WSL环境检查、Node.js/npm配置、npm全局安装命令及首次启动验证,适合开发者快速上手。

NativeRest环境配置要求与完整操作教程
NativeRest环境配置要求与完整操作教程

学习如何配置 NativeRest REST API 客户端。涵盖 Windows/macOS/Linux 安装后的工作区创建、环境变量管理、请求编辑及响应查看步骤,帮助开发者快速完成基础环境搭建与连通性测试。

CSS设置透明度的注意事项有哪些?opacity属性详解
CSS设置透明度的注意事项有哪些?opacity属性详解

深入解析CSS中设置透明度的核心属性opacity,剖析子元素继承、事件穿透、层叠上下文等关键注意事项,并提供与rgba、hsla的实用选型对比。

flutter页面传值到后台的方法及示例代码
flutter页面传值到后台的方法及示例代码

flutter页面传值到后台的完整实现方法及示例代码,帮助读者快速掌握相关技术要点。

Java 8至21新特性代码写法对比:Lambda、Record与Switch
Java 8至21新特性代码写法对比:Lambda、Record与Switch

本文通过具体的旧版与新版代码对比,详细剖析Java 8引入的Lambda表达式、Java 14/16引入的Record类,以及Java 12至21逐步演进完善的Switch表达式与模式匹配,展示代码简化路径与避坑要点。

AI智能体开发培训课程学什么及实战内容介绍
AI智能体开发培训课程学什么及实战内容介绍

系统梳理AI智能体开发培训的核心知识模块、技术栈选型与典型实战项目,解析低代码平台与纯代码框架的差异,提供从零构建可落地智能体的完整学习与实施路径。

Java子类未实现抽象方法编译错误修复指南
Java子类未实现抽象方法编译错误修复指南

针对Java开发中常见的“子类未实现抽象方法”编译错误,深入分析报错原因,提供重写实现、声明抽象子类两种标准修复路径,并总结参数签名、访问修饰符等典型避坑要点。

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

PDF教程
PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

Mac软件 更多
Shapr3D macOS版
Shapr3D macOS版
Mac

Shapr3D是一款面向工业设计、机械工程、建筑概念和三维打印工作流的CAD软件。Mac版采用Parasolid建模内核,支持草图约束、实体建模、工程图、可视化渲染及常见CAD格式交换,并可通过账户在多台设备之间同步项目。

REAPER macOS版
REAPER macOS版
Mac

REAPER是Cockos开发的数字音频工作站,提供多轨音频与MIDI录制、剪辑、处理、混音和母带制作工具。Mac版兼容Intel与Apple芯片,支持AU、VST、VST3、CLAP等插件格式,并提供高度可定制的工作流程。

Ableton Live macOS版
Ableton Live macOS版
Mac

Ableton Live 是面向音乐制作人与现场表演者的数字音频工作站,提供编曲视图、独具特色的现场视图、音频录制、MIDI创作、实时变速、乐器及效果器。Mac版原生支持Apple芯片,并可连接音频接口、MIDI控制器和第三方插件。

WINDOWS 更多
3dmax(3ds max)
3dmax(3ds max)
Windows

Autodesk 3ds Max 是一款专业的三维建模、动画与渲染软件,广泛应用于建筑可视化、游戏开发、影视动画、广告设计和产品展示等领域。

photoshop
photoshop
Windows、macOS 、 iPad

Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

Blender
Blender
Windows、macOS 和 Linux

Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com 网站地图

Copyright ©2018-2026