SpringBoot 接口参数校验(Bean Validation)实战
在开发接口时,参数校验是必不可少的环节:前端传参是否为空、格式是否正确、数值是否合法,都需要后端严格校验,否则很容易出现脏数据、程序异常。
传统的if-else判空不仅代码臃肿,还容易遗漏,维护成本极高。
SpringBoot 官方推荐使用Bean Validation(JSR-380)实现优雅的参数校验,通过注解一键完成校验,配合全局异常处理,让接口更健壮、代码更简洁。
今天就来介绍一下基础注解、实战使用、分组校验、自定义注解、嵌套校验、全局异常处理。
一、为什么要用 Bean Validation?
- 告别繁琐 if-else
:不用写大量判空、判断逻辑,代码更简洁
- 注解式开发
:一个注解完成一类校验,可读性极强
- 校验规则统一
:团队协作无歧义
- 配合全局异常
:校验失败自动返回友好提示,无需手动处理
- 支持复杂场景
:分组校验、自定义校验、嵌套对象校验
二、核心依赖引入
SpringBoot 2.x 版本直接引入以下依赖即可:
1 2<dependency> 3<groupId>org.springframework.bootgroupId> 4<artifactId>spring-boot-starter-validationartifactId> 5dependency> 6 7 8<dependency> 9<groupId>org.springframework.bootgroupId> 10<artifactId>spring-boot-starter-webartifactId> 11dependency>三、最常用校验注解
1. 空与非空校验
@NotBlank:字符串不能为
null,且去除空格后长度大于0(专用字符串)@NotNull:不能为
null,但可以是空字符串、空集合@NotEmpty:不能为
null,且长度/大小大于0(字符串、集合、数组)
2. 数值校验
@Min:数值最小值
@Max:数值最大值
@Positive:正数
@PositiveOrZero:正数或0
@Negative:负数
@NegativeOrZero:负数或0
3. 格式校验
@Email:邮箱格式
@Pattern:正则表达式自定义格式
@Length:字符串长度限制
4. 日期与时间
@Past:必须是过去时间
@PastOrPresent:过去或当前时间
@Future:必须是未来时间
@FutureOrPresent:未来或当前时间
四、单对象参数校验
1. 封装实体类 + 校验注解
1importlombok.Data; 2importjavax.validation.constraints.*; 3 4/** 5* 用户参数接收类 6*/ 7@Data 8publicclassUserDTO{ 9 10@NotBlank(message ="用户ID不能为空") 11privateString userId; 12 13@NotBlank(message ="用户名不能为空") 14@Length(min =2, max =10, message ="用户名长度必须在2-10位之间") 15privateString username; 16 17@NotNull(message ="年龄不能为空") 18@Min(value =18, message ="年龄必须大于等于18岁") 19@Max(value =60, message ="年龄必须小于等于60岁") 20privateInteger age; 21 22@Email(message ="邮箱格式不正确") 23@NotBlank(message ="邮箱不能为空") 24privateString email; 25 26@Pattern(regexp ="^1[3-9]\\d{9}$", message ="手机号格式不正确") 27privateString phone; 28}2. Controller 开启校验(@Valid)
@Valid是开启校验的核心注解,必须添加在参数前:
1importorg.springframework.validation.BindingResult; 2importorg.springframework.web.bind.annotation.PostMapping; 3importorg.springframework.web.bind.annotation.RequestBody; 4importorg.springframework.web.bind.annotation.RequestMapping; 5importorg.springframework.web.bind.annotation.RestController; 6 7importjavax.validation.Valid; 8 9@RestController 10@RequestMapping("/user") 11publicclassUserController{ 12 13/** 14* 新增用户 15*/ 16@PostMapping("/add") 17publicResult<String>addUser(@Valid@RequestBodyUserDTO userDTO){ 18// 校验通过,执行业务逻辑 19returnResult.success("用户新增成功"); 20} 21}五、配合全局异常处理
参数校验失败会抛出MethodArgumentNotValidException,我们在全局异常处理器中捕获,统一返回格式:
1importlombok.extern.slf4j.Slf4j; 2importorg.springframework.web.bind.MethodArgumentNotValidException; 3importorg.springframework.web.bind.annotation.ExceptionHandler; 4importorg.springframework.web.bind.annotation.RestControllerAdvice; 5importjava.util.stream.Collectors; 6 7@Slf4j 8@RestControllerAdvice 9publicclassGlobalExceptionHandler{ 10 11/** 12* 捕获参数校验异常 13*/ 14@ExceptionHandler(MethodArgumentNotValidException.class) 15publicResult<String>handleValidException(MethodArgumentNotValidException e){ 16// 拼接所有错误提示 17String errorMsg = e.getBindingResult().getFieldErrors().stream() 18.map(error -> error.getField()+":"+ error.getDefaultMessage()) 19.collect(Collectors.joining(";")); 20 21 log.error("参数校验异常:{}", errorMsg); 22returnResult.fail(400, errorMsg); 23} 24}校验失败返回示例
1{ 2"code":400, 3"msg":"年龄必须大于等于18岁;邮箱格式不正确", 4"data":null 5}六、分组校验
实际业务中,新增和修改的校验规则不同:
新增:不需要传 ID
修改:必须传 ID
使用分组校验可完美解决。
1. 定义分组接口
1/** 2* 新增分组 3*/ 4publicinterfaceAddGroup{ 5} 6 7/** 8* 修改分组 9*/ 10publicinterfaceUpdateGroup{ 11}2. 实体类标注分组
1@Data 2publicclassUserDTO{ 3 4// 修改时必须传ID,新增时不需要 5@NotBlank(message ="用户ID不能为空", groups =UpdateGroup.class) 6privateString userId; 7 8@NotBlank(message ="用户名不能为空", groups ={AddGroup.class,UpdateGroup.class}) 9privateString username; 10}3. Controller 指定分组
使用@Validated注解指定分组:
1@RestController 2@RequestMapping("/user") 3@Validated 4publicclassUserController{ 5 6// 新增:使用 AddGroup 分组校验 7@PostMapping("/add") 8publicResult<String>add(@Validated(AddGroup.class)@RequestBodyUserDTO userDTO){ 9returnResult.success("新增成功"); 10} 11 12// 修改:使用 UpdateGroup 分组校验 13@PostMapping("/update") 14publicResult<String>update(@Validated(UpdateGroup.class)@RequestBodyUserDTO userDTO){ 15returnResult.success("修改成功"); 16} 17}七、自定义校验注解
当内置注解不满足业务时,可自定义注解,例如校验性别只能是男/女。
1. 自定义注解@Gender
1importjavax.validation.Constraint; 2importjavax.validation.Payload; 3importjava.lang.annotation.*; 4 5@Target({ElementType.FIELD}) 6@Retention(RetentionPolicy.RUNTIME) 7@Constraint(validatedBy =GenderValidator.class)// 绑定校验器 8public@interfaceGender{ 9 10Stringmessage()default"性别只能输入:男/女"; 11 12Class<?>[]groups()default{}; 13 14Class<?extendsPayload>[]payload()default{}; 15}2. 自定义校验器
1importjavax.validation.ConstraintValidator; 2importjavax.validation.ConstraintValidatorContext; 3 4publicclassGenderValidatorimplementsConstraintValidator<Gender,String>{ 5 6@Override 7publicbooleanisValid(String value,ConstraintValidatorContext context){ 8// 校验逻辑 9return"男".equals(value)||"女".equals(value); 10} 11}3. 使用自定义注解
1@Gender 2privateString gender;八、嵌套对象校验
如果参数是嵌套对象,需要在嵌套对象上添加 @Valid才能开启校验:
1@Data 2publicclassUserDTO{ 3@NotBlank 4privateString username; 5 6@Valid// 开启嵌套校验 7@NotNull(message ="地址信息不能为空") 8privateAddressDTO address; 9} 10 11@Data 12classAddressDTO{ 13@NotBlank(message ="详细地址不能为空") 14privateString detail; 15 16@NotBlank(message ="城市不能为空") 17privateString city; 18}九、单个参数校验(非实体类)
如果接口是零散参数,在类上添加@Validated,直接给参数加注解:
1@RestController 2@RequestMapping("/user") 3@Validated 4publicclassUserController{ 5 6@GetMapping("/get") 7publicResult<String>getUser( 8@NotBlank(message ="用户ID不能为空")String userId, 9@NotNull(message ="状态不能为空")Integer status 10){ 11returnResult.success("查询成功"); 12} 13}十、总结
@Valid:开启实体类参数校验
@Validated:开启分组校验、单个参数校验
- 常用注解
:
@NotBlank、@NotNull、@Min、@Max、@Email - 全局异常捕获
:校验失败自动返回友好提示
- 分组校验
:适配新增/修改不同规则
- 自定义注解
:满足复杂业务校验
学会这套参数校验方案,你的接口健壮性、规范性、安全性直接拉满!
