Spring Boot应用篇-JSR参数校验定制
-
T1 - 前言
SpringBoot中默认提供了对JSR(Java Specification Requests)注解的校验支持,但并没有对校验失败抛出的异常进行处理,该博文主要介绍个人对Spring Boot参数校验的一些流程以及处理细节。
SpringBoot默认使用的校验器为hibernate中的HibernateValidator校验器,SpringBoot web/webflux已默认添加hibernate-validator的依赖。 -
T2 - 校验流程
Spring Boot对参数的校验分为对body参数校验与方法普通参数校验,body为VO对象,普通参数为地址栏参数,在Spring中对应的校验处理器也有所不同,主要校验流程图如下:
-
T3 - 处理异常
由T2流程图看出参数校验失败时可能会抛出ConstraintViolationException或MethodArgumentNotValidException异常,以下将对这2个异常类进行简单的讲解与处理。
-
MethodArgumentNotValidException类图(省略部分方法)
parameter主要提供校验失败的方法相关信息(方法对象、方法注释)与参数信息(Parameter对象、字段上的注解)等方法。bindResult为校验失败的绑定信息,主要提供获取校验的对象、字段错误信息、校验失败数目等方法。
知道校验异常中的属性主要信息与作用后便该对其提取出我们所需的信息了,以下为个人对该异常的处理代码Demo:(为了方便了解print了所有FieldError,一般实际中只需提取第一个异常即可):/** * body参数校验错误处理 */ @ExceptionHandler({MethodArgumentNotValidException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) public Object postParamExceptionHandler(MethodArgumentNotValidException exception) { FieldError fieldError = exception.getBindingResult().getFieldError(); String message = fieldError.getDefaultMessage(); // 获取校验对象 System.err.println("getTarget:" + exception.getBindingResult().getTarget().getClass()); // 遍历输出校验失败信息 exception.getBindingResult().getFieldErrors() .forEach(e -> System.out.println(e.getField() + " " + e.getDefaultMessage())); return msg(message.endsWith(";") ? String.format("%s%s", fieldError.getField(), message.substring(0, message.length() - 1)) : message); }
UserVO.java
(注:若JSR注解中不含group而方法参数前的@Validated|@Valid设置了groups那该方法将忽略没有设置groups的JSR注解校验,如username的@NotBlank没有设置groups那username的校验只会发生在UserVO的校验注解没有设置groups的方法中)@Data public class UserVO { @NotBlank(groups = UpdateGroup.class) @Null(groups = InsertGroup.class) private Integer id; @NotBlank(groups = InsertGroup.class) private String username; private String password; @Pattern(regexp = "ENABLE|DISABLE", groups = {InsertGroup.class, UpdateGroup.class}, message = "status须符合正则ENABLE|DISABLE-test") private String status; }
UserBaseController.java
@RestController @RequestMapping("/userBase") @Api @Validated public class UserBaseController { @PostMapping("/") public UserVO add(@RequestBody @Validated(InsertGroup.class) UserVO vo) { return vo; } @PutMapping("/") public UserVO update(@RequestBody @Validated(UpdateGroup.class) UserVO vo) { return vo; } @DeleteMapping("/") public UserVO delete(@RequestParam @Min(0) Integer id, @RequestParam @Pattern(regexp = "AAA|BBB", message = "test必须符合正则AAA|BBB") String test) { return null; } }
调用UserBaseController.add()接口校验信息图如下:
由效果图可以看出字段与信息的拼接还是有点缺陷的,该缺陷个人是通过对ValidationMessages.properties进行一个小动作进行修复,具体在文末再详说(并含校验信息国际化方法)
-
javax.validation.ConstraintViolationException
ConstraintViolationException只包含了ConstraintViolation<?>接口的Set集合,该集合封装了方法普通参数的校验失败信息。ConstraintViolation没有属性,其主要方法如下:
以下为个人对该异常的处理代码Demo:/** * query参数校验错误处理 */ @ExceptionHandler({ConstraintViolationException.class}) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseEntity getParamExceptionHandler(ConstraintViolationException exception) { ConstraintViolation first = new ArrayList<>(exception.getConstraintViolations()).get(0); // 获取校验失败方法所在类 System.out.println("first.getRootBeanClass():" + first.getRootBeanClass()); // 获取失败的参数路径,toString()后格式为method.parameterName System.out.println("first.getPropertyPath():" + first.getPropertyPath()); // 失败信息 System.out.println("first.getMessage():" + first.getMessage()); // 信息模板,对应ValidationMessages.properties的属性名 System.out.println("first.getMessageTemplate():" + first.getMessageTemplate()); return msg(String.format(messageFormat, first.getPropertyPath().toString().split("\\.")[1], first.getMessage())); }
调用UserBaseController.delete()接口校验信息如下:
-
-
T4 - 扩展
hibernate-validator依赖的org.hibernate.validator中包含了各国语言的校验信息模板,对此可以将其所有校验文件copy到我们自己的项目中配置SessionLocalResolver设置i18n语言,且通过在ValidationMessages.properties的校验信息加一个后缀来判断校验失败时抛出的信息是在JSR注解中定义的还是默认的,个人使用的后缀是分号";"。
对于i18与校验信息是否自定义再进行的详细代码可以看以下依赖源码,具体便不介绍了,当然也可直接引入个人项目,返回的信息码(不配默认code)与信息(不配为msg)都可在application.yml中定义。-
Dependency
<dependency> <groupId>io.github.wilson-he</groupId> <artifactId>web-boot-validation</artifactId> <version>1.0.1</version> </dependency>
-
application.yml
swagger: docket: base-package: io.github.validation enabled: true web: common: validation: msg-locale: zh_CN #校验信息为中文 msg-key: msg code-key: code
-
返回效果图
-