# Spring Boot参数校验实践 ## 技术栈 - Spring Boot 2.3.3.RELEASE - Hibernate Validator ## 概述 `@Validated` 是Spring框架提供的注解,用于对方法参数进行数据校验。配合Hibernate Validator的约束注解,可以轻松实现对数据的验证。 `@Validated` 可以作用于类、方法和参数上。 ```java @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Validated { Class[] value() default {}; } ``` **HTTP状态码建议** 校验失败时,推荐返回 `400 Bad Request`。 **常用校验注解**  ## 项目依赖 ```xml org.springframework.boot spring-boot-starter-validation ``` ## 全局异常处理 若不进行异常处理,`@Validated` 的默认异常信息较为冗长: ```java 2020-09-05 21:48:38.106 WARN 9796 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.validateddemo.controller.DemoController.validatedDemo1(com.example.validateddemo.entity.dto.UseDto): [Field error in object 'useDto' on field 'username': rejected value [null]; codes [NotBlank.useDto.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [useDto.username,username]; arguments []; default message [username]]; default message [用户名不能为空!]] ] ``` 因此,我们需要创建全局异常处理器,统一处理参数校验异常: ```java package com.example.validateddemo.handler; import com.example.validateddemo.base.Result; import com.example.validateddemo.enums.ResultEnum; import com.example.validateddemo.utils.ResultUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.List; /** * 参数校验异常处理器 * @author He Changjie on 2020/9/5 */ @Slf4j @ControllerAdvice public class ValidatedExceptionHandler { /** * 处理@Validated参数校验失败异常 * @param exception 异常类 * @return 响应结果 */ @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) public Result exceptionHandler(MethodArgumentNotValidException exception){ BindingResult result = exception.getBindingResult(); StringBuilder stringBuilder = new StringBuilder(); if (result.hasErrors()) { List errors = result.getAllErrors(); if (errors != null) { errors.forEach(p -> { FieldError fieldError = (FieldError) p; log.warn("Bad Request Parameters: dto entity [{}],field [{}],message [{}]",fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage()); stringBuilder.append(fieldError.getDefaultMessage()); }); } } return ResultUtil.validatedException(stringBuilder.toString()); } } ``` ## 基础参数校验 ### 实体类 ```java package com.example.validateddemo.entity.dto; import lombok.Data; import javax.validation.constraints.*; /** * 用户数据传输对象 * @author He Changjie on 2020/9/5 */ @Data public class User1Dto { @NotBlank(message = "用户名不能为空!") private String username; @NotBlank(message = "性别不能为空!") private String gender; @Min(value = 1, message = "年龄有误!") @Max(value = 120, message = "年龄有误!") private int age; @NotBlank(message = "地址不能为空!") private String address; @Email(message = "邮箱有误!") private String email; @Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$", message = "手机号码有误!") private String mobile; } ``` ### 控制器 ```java package com.example.validateddemo.controller; import com.example.validateddemo.entity.dto.User1Dto; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1") public class Demo1Controller { @PostMapping("/insert") public String validatedDemo1(@Validated @RequestBody User1Dto user1Dto){ System.out.println(user1Dto); return "success"; } } ``` ### 测试结果 1. **参数校验通过**  2. **参数校验不通过**  ## 嵌套参数验证 验证实体中包含的其他对象或集合。 ### 实体类 ```java package com.example.validateddemo.entity.dto; import lombok.Data; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; @Data public class Team1Dto { @NotBlank(message = "队伍名称不能为空!") private String name; @NotNull(message = "队伍人员不能为空!") @Valid private List userList; @NotNull(message = "队伍负责人不能为空!") @Valid private User1Dto user; } ``` ### 控制器方法 ```java @PostMapping("/insert2") public Result validatedDemo2(@Validated @RequestBody Team1Dto team1Dto){ return ResultUtil.success(team1Dto); } ``` ### 测试结果 1. **参数验证通过**  2. **参数验证不通过**   ## 分组参数验证 通过分组机制,在不同场景下应用不同的校验规则。 ### 分组接口 ```java package com.example.validateddemo.interfaces; public interface Group1 {} ``` ```java package com.example.validateddemo.interfaces; public interface Group2 {} ``` ### 实体类 ```java package com.example.validateddemo.entity.dto; import com.example.validateddemo.interfaces.Group1; import com.example.validateddemo.interfaces.Group2; import lombok.Data; import javax.validation.constraints.*; @Data public class User2Dto { @NotBlank(message = "用户名不能为空!", groups = {Group1.class}) private String username; @NotBlank(message = "性别不能为空!") private String gender; @Min(value = 1, message = "年龄有误!", groups = {Group1.class}) @Max(value = 120, message = "年龄有误!", groups = {Group2.class}) private int age; @NotBlank(message = "地址不能为空!") private String address; @Email(message = "邮箱有误!", groups = {Group2.class}) private String email; @Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$", message = "手机号码有误!", groups = {Group2.class}) private String mobile; } ``` ### 控制器方法 ```java @PostMapping("/insert4") public Result validatedDemo4(@Validated(Group1.class) @RequestBody User2Dto user2Dto){ return ResultUtil.success(user2Dto); } @PostMapping("/insert5") public Result validatedDemo5(@Validated(Group2.class) @RequestBody User2Dto user2Dto){ return ResultUtil.success(user2Dto); } ``` ## @Valid与@Validated区别 **@Valid源码** ```java @Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Valid {} ``` **@Validated源码** ```java @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Validated { Class[] value() default {}; } ``` **主要区别** 1. **分组功能**:`@Validated`支持分组,`@Valid`不支持 2. **使用范围**: - `@Valid`可用于方法、字段、构造函数、参数和类型使用 - `@Validated`可用于类型、方法和参数,但不能用于字段 3. **嵌套验证**:`@Valid`可用于字段,因此支持嵌套验证;`@Validated`不能直接用于字段 **总结**:在Spring项目中,通常使用`@Validated`进行方法参数校验,使用`@Valid`进行嵌套对象校验。 --- **源码地址**:[码云项目地址] > 原创不易,转载请注明来源 > 文章来源:https://blog.csdn.net/qq_32352777/article/details/108424932
评论