SpringValidation最佳实践及其实现原理
⼀、背景
Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。
hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。
Spring Validation是对hibernate validation的⼆次封装,⽤于⽀持spring mvc参数⾃动校验。
⼆、引⼊依赖
如果spring-boot版本⼩于2.3.x,spring-boot-starter-web会⾃动传⼊hibernate-validator依赖。如果spring-boot版本⼤于2.3.x,则需要⼿动引⼊依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>
对于web服务来说,为防⽌⾮法参数对业务造成影响,在Controller层⼀定要做参数校验的!⼤部分情况下,请求参数分为如下两种形式:POST、PUT请求,使⽤requestBody传递参数;
GET请求,使⽤requestParam/PathVariable传递参数。
下⾯我们简单介绍下requestBody和requestParam/PathVariable的参数校验实战!
三、requestBody参数校验
POST、PUT请求⼀般会使⽤requestBody传递参数
这种情况下,后端使⽤DTO对象进⾏接收
只要给DTO对象加上@Validated注解就能实现⾃动参数校验
⽐如,有⼀个保存User的接⼝,要求userName长度是2-10,account和password字段长度是6-20。
如果校验失败,会抛出MethodArgumentNotValidException异常,Spring默认会将其转为400(Bad Request)请求。
DTO表⽰数据传输对象(Data Transfer Object),⽤于服务器和客户端之间交互传输使⽤的。在spring-web项⽬中可以表⽰⽤于接收请求参数的Bean对象。
在DTO字段上声明约束注解
@Data
public class UserDTO {
private Long userId;
@NotNull
@Length(min = 2, max = 10)
private String userName;
@NotNull
@Length(min = 6, max = 20)
private String account;
@NotNull
@Length(min = 6, max = 20)
private String password;
}
在⽅法参数上声明校验注解
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
// 校验通过,才会执⾏业务逻辑处理
return Result.ok();
}
这种情况下,使⽤@Valid和@Validated都可以。
四、requestParam/PathVariable参数校验
spring mvc和boot区别GET请求⼀般会使⽤requestParam/PathVariable传参。
如果参数⽐较多(⽐如超过6个),还是推荐使⽤DTO对象接收。
否则,推荐将⼀个个参数平铺到⽅法⼊参中。
在这种情况下,必须在Controller类上标注@Validated注解,并在⼊参上声明约束注解(如@Min等)。
如果校验失败,会抛出ConstraintViolationException异常。
@RequestMapping("/api/user")
@RestController
@Validated
public class UserController {
// 路径变量
@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
// 校验通过,才会执⾏业务逻辑处理
UserDTO userDTO = new UserDTO();
userDTO.setUserId(userId);
userDTO.setAccount("11111111111111111");
userDTO.setUserName("xixi");
userDTO.setAccount("11111111111111111");
return Result.ok(userDTO);
}
// 查询参数
@GetMapping("getByAccount")
public Result getByAccount(@Length(min = 6, max = 20) @NotNull String  account) {
// 校验通过,才会执⾏业务逻辑处理
UserDTO userDTO = new UserDTO();
userDTO.setUserId(10000000000000003L);
userDTO.setAccount(account);
userDTO.setUserName("xixi");
userDTO.setAccount("11111111111111111");
return Result.ok(userDTO);
}
}
五、统⼀异常处理
前⾯说过,如果校验失败,会抛出MethodArgumentNotValidException或者ConstraintViolationException异常。
在实际项⽬开发中,通常会⽤统⼀异常处理来返回⼀个更友好的提⽰。
⽐如我们系统要求⽆论发送什么异常,http的状态码必须返回200,由业务码去区分系统的异常情况。
@RestControllerAdvice
public class CommonExceptionHandler {
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("校验失败:");
for (FieldError fieldError : FieldErrors()) {
sb.Field()).append(":").DefaultMessage()).append(", ");
}
String msg = sb.toString();
return Result.fail(BusinessCode.参数校验失败, msg);
}
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleConstraintViolationException(ConstraintViolationException ex) {
return Result.fail(BusinessCode.参数校验失败, ex.getMessage());
}
}
六、进阶使⽤
分组校验
在实际项⽬中,可能多个⽅法需要使⽤同⼀个DTO类来接收参数,⽽不同⽅法的校验规则很可能是不⼀样的。
这个时候,简单地在DTO类的字段上加约束注解⽆法解决这个问题。
因此,spring-validation⽀持了分组校验的功能,专门⽤来解决这类问题。
还是上⾯的例⼦,⽐如保存User的时候,UserId是可空的,但是更新User的时候,UserId的值必须>=10000000000000000L;
其它字段的校验规则在两种情况下⼀样。
约束注解上声明适⽤的分组信息groups
这个时候使⽤分组校验的代码⽰例如下:
@Data
public class UserDTO {
@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
/**
* 保存的时候校验分组
*/
public interface Save {
}
/**
* 更新的时候校验分组
*/
public interface Update {
}
}
@Validated注解上指定校验分组
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {
// 校验通过,才会执⾏业务逻辑处理
return Result.ok();
}
@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {
// 校验通过,才会执⾏业务逻辑处理
return Result.ok();
}
嵌套校验
前⾯的⽰例中,DTO类⾥⾯的字段都是基本数据类型和String类型。
但是实际场景中,有可能某个字段也是⼀个对象,这种情况先,可以使⽤嵌套校验。
⽐如,上⾯保存User信息的时候同时还带有Job信息。需要注意的是,此时DTO类的对应字段必须标记@Valid注解。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。