Spring Boot参数校验避坑指南从入门到完全精通

 更新时间:2026年09月02日 10:18:40   作者:露天赏雪  
业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验来满足我们的需求,这篇文章主要介绍了Spring Boot参数校验避坑指南从入门到完全精通的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

在Spring Boot接口开发中,参数校验是保障系统稳定性的重要环节。不规范的参数校验会导致代码冗余、逻辑混乱,甚至引发系统异常。本文从入门到精通,详细讲解Spring Boot参数校验的核心用法、常见坑点及解决方案,附完整代码示例。

一、参数校验入门:基础用法

Spring Boot参数校验基于JSR-380规范(Validation API),通过注解方式简化参数校验逻辑,无需编写大量if-else判断。

1. 核心依赖

Spring Boot 2.3+已内置Validation API依赖,无需额外引入:

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

若使用 Spring Boot 2.3 以下版本,需手动添加此依赖。

2. 常用校验注解

注解作用场景示例
@NotBlank字符串非空(不允许空白)@NotBlank (message = "用户名不能为空")
@NotNull对象 / 基本类型非空@NotNull (message = "年龄不能为空")
@NotEmpty集合 / 数组非空(长度 > 0)@NotEmpty (message = "爱好不能为空")
@Min数字最小值@Min (value = 18, message = "年龄不能小于 18")
@Max数字最大值@Max (value = 60, message = "年龄不能大于 60")
@Size字符串 / 集合长度范围@Size (min = 2, max = 20, message = "用户名长度 2-20")
@Email邮箱格式校验@Email (message = "邮箱格式不正确")
@Pattern正则表达式校验@Pattern (regexp = "^1 [3-9]\d {9}$", message = "手机号格式不正确")

3. 基础用法示例

(1)路径参数校验

在 Controller 类上添加@Validated注解,路径参数上添加校验注解:

package com.demo.controller;

import com.demo.common.Result;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.Min;
import javax.validation.constraints.Pattern;

@RestController
@RequestMapping("/user")
@Validated // 必须添加,否则路径参数校验不生效
public class UserController {

    // 路径参数校验:用户ID≥1,手机号格式正确
    @GetMapping("/{id}/{phone}")
    public Result<?> getUser(
            @PathVariable @Min(value = 1, message = "用户ID不能小于1") Integer id,
            @PathVariable @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") String phone) {
        
        return Result.success("查询成功");
    }
}

(2)请求参数校验(JSON 格式)

请求参数封装为 DTO 类,在 DTO 字段上添加校验注解,Controller 方法参数添加@Validated注解:

package com.demo.controller;

import com.demo.common.Result;
import lombok.Data;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@RestController
@RequestMapping("/user")
public class UserController {

    // JSON参数校验
    @PostMapping("/add")
    public Result<?> addUser(@RequestBody @Validated UserAddRequest request) {
        return Result.success("用户添加成功");
    }

    // 请求DTO
    @Data
    public static class UserAddRequest {
        @NotBlank(message = "用户名不能为空")
        @Size(min = 2, max = 20, message = "用户名长度必须在2-20之间")
        private String username;

        @NotBlank(message = "邮箱不能为空")
        @Email(message = "邮箱格式不正确")
        private String email;

        @NotBlank(message = "手机号不能为空")
        @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
        private String phone;
    }
}

(3)请求参数校验(表单格式)

表单参数直接写在 Controller 方法参数上,添加校验注解,Controller 类添加@Validated注解:

@RestController
@RequestMapping("/user")
@Validated // 必须添加
public class UserController {

    // 表单参数校验
    @PostMapping("/login")
    public Result<?> login(
            @RequestParam @NotBlank(message = "用户名不能为空") String username,
            @RequestParam @NotBlank(message = "密码不能为空") @Size(min = 6, message = "密码长度不能小于6位") String password) {
        
        return Result.success("登录成功");
    }
}

二、参数校验进阶:常见场景解决方案

1. 分组校验(同一 DTO 适配多个接口)

场景:同一 DTO 需适配多个接口(如添加用户和修改用户),不同接口的校验规则不同。

解决方案:使用分组校验

(1)定义分组接口(空接口,仅用于标记):

// 添加用户分组
public interface AddGroup {}

// 修改用户分组
public interface UpdateGroup {}

(2)DTO 字段上指定分组:

@Data
public class UserDTO {
    // 修改用户时校验ID,添加用户时不校验
    @NotNull(message = "用户ID不能为空", groups = UpdateGroup.class)
    private Integer id;

    // 添加和修改都校验用户名
    @NotBlank(message = "用户名不能为空", groups = {AddGroup.class, UpdateGroup.class})
    private String username;

    // 添加用户时校验邮箱,修改用户时不校验
    @Email(message = "邮箱格式不正确", groups = AddGroup.class)
    private String email;
}

(3)Controller 方法指定分组:

// 添加用户(使用AddGroup分组校验)
@PostMapping("/add")
public Result<?> addUser(@RequestBody @Validated(AddGroup.class) UserDTO dto) {
    return Result.success("添加成功");
}

// 修改用户(使用UpdateGroup分组校验)
@PostMapping("/update")
public Result<?> updateUser(@RequestBody @Validated(UpdateGroup.class) UserDTO dto) {
    return Result.success("修改成功");
}

2. 嵌套校验(DTO 包含子 DTO)

场景:DTO 中包含子 DTO,需要校验子 DTO 的字段。

解决方案:在子 DTO 字段上添加@Valid注解

// 子DTO(地址信息)
@Data
public class AddressDTO {
    @NotBlank(message = "省份不能为空")
    private String province;

    @NotBlank(message = "城市不能为空")
    private String city;
}

// 父DTO(用户信息)
@Data
public class UserDTO {
    @NotBlank(message = "用户名不能为空")
    private String username;

    // 嵌套校验:添加@Valid注解
    @Valid
    @NotNull(message = "地址信息不能为空")
    private AddressDTO address;
}

3. 自定义校验注解(满足特殊业务需求)

场景:内置校验注解无法满足需求(如校验手机号格式、身份证号格式)。

解决方案:自定义校验注解

(1)定义自定义校验注解:

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

// 注解作用目标:字段、方法参数
@Target({ElementType.FIELD, ElementType.PARAMETER})
// 注解保留策略:运行时
@Retention(RetentionPolicy.RUNTIME)
// 标记为校验注解
@Constraint(validatedBy = PhoneValidator.class) // 指定校验器
public @interface Phone {
    // 校验失败提示消息
    String message() default "手机号格式不正确";

    // 分组
    Class<?>[] groups() default {};

    // 负载
    Class<? extends Payload>[] payload() default {};
}

(2)实现校验器(ConstraintValidator):

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;

// 泛型:第一个是自定义注解,第二个是校验字段类型
public class PhoneValidator implements ConstraintValidator<Phone, String> {

    // 手机号正则表达式
    private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");

    @Override
    public boolean isValid(String phone, ConstraintValidatorContext context) {
        // 字段为null时,返回true(若需要非空校验,需配合@NotBlank注解)
        if (phone == null) {
            return true;
        }
        // 匹配正则表达式
        return PHONE_PATTERN.matcher(phone).matches();
    }
}

(3)使用自定义注解:

@Data
public class UserDTO {
    @NotBlank(message = "用户名不能为空")
    private String username;

    // 使用自定义校验注解
    @Phone(message = "手机号格式不正确")
    private String phone;
}

三、参数校验避坑指南:10 个常见坑点

坑点 1:Controller 类未添加@Validated注解,路径参数 / 表单参数校验不生效

现象:路径参数或表单参数添加了校验注解,但参数不合法时未抛出异常。

原因:路径参数和表单参数的校验需要在 Controller 类上添加@Validated注解,否则校验不生效。解决方案:在 Controller 类上添加@Validated注解。

坑点 2:JSON 参数校验未在 DTO 字段上添加注解,或未在参数上添加@Validated

现象:JSON 参数不合法时,未抛出校验异常。

原因:JSON 参数校验需满足两个条件:DTO 字段添加校验注解 + Controller 方法参数添加@Validated注解。

解决方案

// 正确用法
@PostMapping("/add")
public Result<?> addUser(@RequestBody @Validated UserDTO request) {
    // ...
}

坑点 3:@NotBlank、@NotNull、@NotEmpty混用

现象:校验逻辑不符合预期(如允许空白字符串、允许空集合)。

原因:三个注解的作用场景不同,混用会导致校验失效。

解决方案

  • @NotBlank:用于字符串,不允许 null 和空白字符串(""," ");
  • @NotNull:用于对象 / 基本类型,不允许 null;
  • @NotEmpty:用于集合 / 数组 / 字符串,不允许 null 且长度 > 0(字符串不允许 "",但允许" ")。

坑点 4:嵌套校验未添加@Valid注解

现象:子 DTO 的字段校验不生效。

原因:父 DTO 中的子 DTO 字段未添加@Valid注解,嵌套校验不生效。

解决方案:在子 DTO 字段上添加@Valid注解。

坑点 5:自定义校验注解未指定校验器,或校验器未实现ConstraintValidator

现象:使用自定义校验注解时,校验不生效。

原因:自定义校验注解需通过@Constraint(validatedBy = 校验器.class)指定校验器,且校验器需实现ConstraintValidator接口。

解决方案:正确配置自定义注解的validatedBy属性,确保校验器实现ConstraintValidator接口。

坑点 6:校验异常未被全局异常处理器捕获,返回默认错误页面

现象:参数不合法时,返回 Spring Boot 默认的错误页面,而非统一格式的 JSON 响应。

原因:校验异常未被全局异常处理器捕获,默认返回错误页面。

解决方案:在全局异常处理器中添加MethodArgumentNotValidException(JSON 参数校验异常)和ConstraintViolationException(路径参数 / 表单参数校验异常)的处理逻辑。

补充全局异常处理器代码:

// 处理路径参数/表单参数校验异常
@ExceptionHandler(ConstraintViolationException.class)
public Result<?> handleConstraintViolationException(ConstraintViolationException e) {
    // 获取校验失败信息
    String errorMsg = e.getConstraintViolations().stream()
            .map(ConstraintViolation::getMessage)
            .collect(Collectors.joining(","));
    log.error("参数校验失败:{}", errorMsg, e);
    return Result.fail(ResultCode.PARAM_ERROR.getCode(), errorMsg);
}

// 处理JSON参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    String errorMsg = e.getBindingResult().getFieldErrors().stream()
            .map(fieldError -> fieldError.getField() + ":" + fieldError.getDefaultMessage())
            .collect(Collectors.joining(","));
    log.error("参数校验失败:{}", errorMsg, e);
    return Result.fail(ResultCode.PARAM_ERROR.getCode(), errorMsg);
}

坑点 7:分组校验时,Controller 方法未指定分组

现象:分组校验不生效,所有校验规则都被执行。

原因:Controller 方法未指定分组,默认执行所有无分组的校验规则。

解决方案:在 Controller 方法参数的@Validated注解中指定分组:

@PostMapping("/add")
public Result<?> addUser(@RequestBody @Validated(AddGroup.class) UserDTO dto) {
    // ...
}

坑点 8:@Pattern注解正则表达式写错,导致校验失效

现象:符合预期格式的参数被判定为不合法,或不符合格式的参数被判定为合法。

原因:正则表达式编写错误(如手机号正则少写位数、特殊字符未转义)。

解决方案

  • 编写正则表达式后,先通过单元测试验证;
  • 复杂正则表达式可使用在线工具(如 Regex101)验证。

坑点 9:校验注解的message参数包含特殊字符,导致前端解析异常

现象:校验失败时,前端解析错误消息失败。

原因message参数包含引号、换行符等特殊字符,JSON 序列化时出现格式错误。

解决方案message参数中避免使用特殊字符,若必须使用,需进行转义。

坑点 10:忽略 null 值校验,导致空指针异常

现象:参数为 null 时,未被校验,后续业务逻辑中调用参数的方法抛出空指针异常。

原因:仅使用了@Size@Email等注解,未使用@NotNull@NotBlank注解,允许参数为 null。

解决方案:根据业务需求,给必填参数添加@NotNull@NotBlank注解。

四、总结

到此这篇关于Spring Boot参数校验避坑指南从入门到完全精通的文章就介绍到这了,更多相关Spring Boot参数校验避坑内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决springmvc项目中使用过滤器来解决请求方式为post时出现乱码的问题

    解决springmvc项目中使用过滤器来解决请求方式为post时出现乱码的问题

    这篇文章主要介绍了springmvc项目中使用过滤器来解决请求方式为post时出现乱码的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • Java超详细大文件分片上传代码

    Java超详细大文件分片上传代码

    文件上传是一个很常见的功能。在项目开发过程中,我们通常都会使用一些成熟的上传组件来实现对应的功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧<BR>
    2022-06-06
  • java实现微信扫码登录第三方网站功能(原理和代码)

    java实现微信扫码登录第三方网站功能(原理和代码)

    为避免繁琐的注册登陆,很多平台和网站都会实现三方登陆的功能,增强用户的粘性。这篇文章主要介绍了java实现微信扫码登录第三方网站功能(原理和代码),避免做微信登录开发的朋友们少走弯路
    2022-12-12
  • Spring Cloud Alibaba实现服务的无损下线功能(案例讲解)

    Spring Cloud Alibaba实现服务的无损下线功能(案例讲解)

    这篇文章主要介绍了Spring Cloud Alibaba实现服务的无损下线功能 ,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • JAVA三种异常处理机制的具体使用

    JAVA三种异常处理机制的具体使用

    异常是程序在编译或执行的过程中可能出现的问题,本文主要介绍了JAVA三种异常处理机制的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06
  • mybatis-plus雪花算法自动生成机器id原理及源码

    mybatis-plus雪花算法自动生成机器id原理及源码

    Mybatis-Plus是一个Mybatis的增强工具,它在Mybatis的基础上做了增强,却不做改变,Mybatis-Plus是为简化开发、提高开发效率而生,但它也提供了一些很有意思的插件,比如SQL性能监控、乐观锁、执行分析等,下面一起看看mybatis-plus雪花算法自动生成机器id原理解析
    2021-06-06
  • SpringBoot接口如何统一异常处理

    SpringBoot接口如何统一异常处理

    这篇文章主要介绍了SpringBoot接口如何统一异常处理,SpringBoot接口如何对异常进行统一封装,并统一返回呢?以下文的参数校验为例,如何优雅的将参数校验的错误信息统一处理并封装返回呢,感兴趣的下下伙伴可以一同参考一下
    2022-07-07
  • java实现工资管理简单程序

    java实现工资管理简单程序

    这篇文章主要为大家详细介绍了java实现工资管理简单程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • 如何通过Java实现加密、解密Word文档

    如何通过Java实现加密、解密Word文档

    这篇文章主要介绍了如何通过Java实现加密、解密Word文档,对一些重要文档,常需要对文件进行加密,查看文件时,需要正确输入密码才能打开文件。下面介绍了一种比较简单的方法给Word文件加密以及如何给已加密的Word文件解除密码保护,需要的朋友可以参考下
    2019-07-07
  • MyBatis Generator生成的$ sql是否存在注入风险详解

    MyBatis Generator生成的$ sql是否存在注入风险详解

    这篇文章主要介绍了MyBatis Generator生成的$ sql是否存在注入风险详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12

最新评论