请求参数校验
快速开始
从SpringBoot 2.3开始,校验包被独立成了一个starter
组件(参见:validation-starter-no-longer-included-in-web-starters),所以需要引入如下依赖:
<!--校验组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
参数校验非常简单,首先在待校验字段上增加校验规则注解
public class UserVO {
@NotNull(message = "age 不能为空")
private Integer age;
}
然后在controller
方法中添加@Validated
和用于接收错误信息的BindingResult
就可以了,于是有了第一版:
public String add1(@Validated UserVO userVO, BindingResult result) {
List<FieldError> fieldErrors = result.getFieldErrors();
if(!fieldErrors.isEmpty()){
return fieldErrors.get(0).getDefaultMessage();
}
return "OK";
}
Validation 内置注解
@Null
被注解的元素必须是null
@NotNull
被注解的元素不能为null,可以为空字符串
@NotBlank
只能用于String
上,不能为null
,而且调用trim()
,长度必须大于0 倍注解的String非空
@NotEmpty
不能为null
,字符串长度不能为0,集合长度不能为0
@Valid和@Validated区别
提供者
JSR-303规范
Spring
是否支持分组
不支持
支持
标注位置
METHOD,FIELD,CONSTRUCTOR,TYPE_USE
TYPE,METHOD,PARAMETER
嵌套校验
支持
不支持
Spring Validation 默认会校验完所有字段,然后才抛出异常,可以通过一些简单的配置,开启Fail Fast模式,一旦校验失败就立即返回。
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
自定义校验
Spring Validation提供的默认注解并不能实际开发中的需要,这时我们可以自定义校验来满足我们的需求。
例如,校验ipv4
地址是否合法。
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EncryptIdValidator.class})
public @interface EncryptId {
// 默认错误消息
String message() default "ipv4 格式错误";
// 分组
Class<?>[] groups() default {};
// 负载
Class<? extends Payload>[] payload() default {};
}
这样我们就可以使用@Ipv4
进行参数校验了!
分组校验功能
一般我们一个模块会实现增删改查等多个功能,每个功能接口所需要检验的DTO
也不太一样,我们可能选择定义多个DTO
,然后对每个DTO
添加不同的校验规则。但是在某些情况下两个不同的接口会复用相同的DTO
,例如用户的新增和修改,参数大致相同,可复用相同的DTO
,但是我们需要对新增和修改的参数校验进行区分。为此,Validation
为我们提供了一个分组检验的功能。
定义一个校验分组类
public class ValidationGroups {
public interface Insert{}
public interface Update{}
}
@Data
public class AddCourseDto {
@NotEmpty(message = "新增用户名不能为空", groups = {ValidationGroups.Insert.class})
@NotEmpty(message = "修改用户名不能为空", groups = {ValidationGroups.Update.class})
private String name;
@NotEmpty(message = "新增用户名手机不能为空", groups = {ValidationGroups.Insert.class})
private String mobile;
}
在name
属性上定义了两个分组,会在controller
指定分组的时候走到不同的校验规则上,提示的信息也会不一样,并且只有在新增分组时,mobile
属性才不能为空。
在
controller
中指定校验分组
@RequiredArgsConstructor
@RestController
public class CourseBaseInfoController {
private final UserService userService;
@PostMapping("/user")
public CourseBaseInfoDto createUser(@RequestBody @Validated(value = ValidationGroups.Insert.class) AddUserDto addUserDto) {
return userService.createUser(1, addUserDto);
}
@PatchMapping("/user")
public CourseBaseInfoDto updateUser(@RequestBody @Validated(value = ValidationGroups.Update.class) AddUserDto addUserDto) {
return userService.createUser(1, addUserDto);
}
}
校验异常统一处理
MethodArgumentTypeMismatchException
在使用普通行参接收时,当请求传递的请求参数类型不能被接收参数的类型正确转换时,会抛出MethodArgumentTypeMismatchException
异常
@RequestMapping("/api/user")
@Validated
public class UserController {
/**
* ✅ /api/user/query?age=1
* ❎ /api/user/query?age=abc
*/
@GetMapping("/query")
public ResponseEntity<Void> test(Integer age) {
return ResponseEntity.ok().build();
}
}
异常捕获:
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResultData<?> parameterExceptionHandler(MethodArgumentTypeMismatchException exception, HttpServletRequest request) {
log.error("请求参数类型转换失败! {} => {}", request.getRequestURI(), exception.getMessage());
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("filed", exception.getName());
String[] split = exception.getMessage().split(": ");
if (split.length > 1) {
map.put("value", split[1].substring(1, split[1].length() - 1));
}
return ResultData.fail(HttpStatus.BAD_REQUEST.value(), "参数不合法", map);
}
MissingServletRequestParameterException
在使用@RequestParam
注解接收参数时,当请求缺失请求参数时,会抛出MissingServletRequestParameterException
异常
/**
* ✅ /api/user/query?age=1
* ❎ /api/user/query
*/
@GetMapping("/code")
public ResponseEntity<Void> test(@RequestParam("age") String age) {
return ResponseEntity.ok().build();
}
异常捕获
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResultData<?> parameterMissingExceptionHandler(MissingServletRequestParameterException exception, HttpServletRequest request) {
log.error("请求参数绑定异常! {} => {}", request.getRequestURI(), exception.getMessage());
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("filed", exception.getParameterName());
map.put("required", exception.getParameterType());
return ResultData.fail(HttpStatus.BAD_REQUEST.value(), "参数不合法", map);
}
ConstraintViolationException
在对普通行参进行Validation
验证时,在请求中,缺失请求参数时,会抛出ConstraintViolationException
异常。要在类上添加@Validated
注解才能生效。
/**
* ✅ /api/user/query?age=1
* ❎ /api/user/query
*/
@RequestMapping("/api/user")
@Validated
public class UserController {
@GetMapping("/query")
public ResponseEntity<Void> test(@NotNull age) {
return ResponseEntity.ok().build();
}
}
异常捕获
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResultData<?> parameterMissingExceptionHandler(MissingServletRequestParameterException exception, HttpServletRequest request) {
log.error("请求参数绑定异常! {} => {}", request.getRequestURI(), exception.getMessage());
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("filed", exception.getParameterName());
map.put("required", exception.getParameterType());
return ResultData.fail(HttpStatus.BAD_REQUEST.value(), "参数不合法", map);
}
MethodArgumentNotValidException
在使用JavaBean
接收参数时,当请求参数校验失败时,会抛出MethodArgumentNotValidException
异常
/**
* ✅ /api/user/query?age=1
* ❎ /api/user/query
*/
@RequestMapping("/api/user")
@Validated
public class UserController {
@GetMapping("/query")
public ResponseEntity<Void> test(UserDto userDto) {
return ResponseEntity.ok().build();
}
}
异常捕获
public ResultData<?> parameterExceptionHandler(MethodArgumentNotValidException exception, HttpServletRequest request) {
log.error("请求参数校验失败! {} => {}", request.getRequestURI(), exception.getMessage());
// 获取异常信息
BindingResult exceptions = exception.getBindingResult();
// 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
List<Object> list = new ArrayList<>();
if (exceptions.hasErrors()) {
List<FieldError> fieldErrors = exceptions.getFieldErrors();
if (!fieldErrors.isEmpty()) {
fieldErrors.forEach(error -> {
String field = error.getField();
String message = error.getDefaultMessage();
if (message != null) {
list.add(Map.of("filed", field, "message", message));
}
});
return ResultData.fail(HttpStatus.BAD_REQUEST.value(), "参数不合法", list);
}
}
return ResultData.fail(HttpStatus.BAD_REQUEST.value(), "参数不合法", list);
}
HttpMessageNotReadableException
请求体无法被正确解析
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResultData<?> processException(HttpMessageNotReadableException exception, HttpServletRequest request) {
log.error("请求体无法被正确解析! {}, {}", request.getRequestURI(), exception.getMessage());
return ResultData.fail(HttpStatus.BAD_REQUEST.value(), "参数不合法");
}
UnexpectedTypeException
注解使用错,例如:将@NotBlank
注解放在任何非字符串类型的字段中。
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(UnexpectedTypeException.class)
public ResultData<?> parameterExceptionHandler(UnexpectedTypeException exception, HttpServletRequest request) {
log.error("注解使用错误! {} => {}", request.getRequestURI(), exception.getMessage());
return ResultData.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务端错误");
}
最后更新于
这有帮助吗?