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 (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时出现乱码的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2020-08-08
Spring Cloud Alibaba实现服务的无损下线功能(案例讲解)
这篇文章主要介绍了Spring Cloud Alibaba实现服务的无损下线功能 ,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2023-03-03
MyBatis Generator生成的$ sql是否存在注入风险详解
这篇文章主要介绍了MyBatis Generator生成的$ sql是否存在注入风险详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-12-12


最新评论