SpringBoot实现各种参数校验
简单使⽤
引⼊依赖
requestBody参数校验
requestParam/PathVariable参数校验
统⼀异常处理
进阶使⽤
分组校验
嵌套校验
集合校验
⾃定义校验
编程式校验
快速失败(Fail Fast)
@Valid和@Validated区别
实现原理
requestBody参数校验实现原理
⽅法级别的参数校验实现原理
之前也写过⼀篇关于Spring Validation使⽤的⽂章,不过⾃我感觉还是浮于表⾯,本次打算彻底搞懂Spring Validation。本⽂会详细介绍Spring Validation各种场景下的最佳实践及其实现原理,死磕到底!
简单使⽤
Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。
Spring Validation是对hibernate validation的⼆次封装,⽤于⽀持spring mvc参数⾃动校验。接下来,我们以spring-boot项⽬为例,介绍Spring Validation的使⽤。
引⼊依赖
如果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参数校验
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注解。
@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;
@NotNull(groups = {Save.class, Update.class})
@Valid
private Job job;
@Data
public static class Job {
@Min(value = 1, groups = Update.class)
private Long jobId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String jobName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String position;
validation框架}
/**
* 保存的时候校验分组
*/
public interface Save {
}
/
**
* 更新的时候校验分组
*/
public interface Update {
}
}
嵌套校验可以结合分组校验⼀起使⽤。还有就是嵌套集合校验会对集合⾥⾯的每⼀项都进⾏校验,例如List<Job>字段会对这个list⾥⾯的每⼀个Job对象都进⾏校验
集合校验
如果请求体直接传递了json数组给后台,并希望对数组中的每⼀项都进⾏参数校验。此时,如果我们直接使⽤java.util.Collection下的list 或者set来接收数据,参数校验并不会⽣效!我们可以使⽤⾃定义list集合来接收参数:
包装List类型,并声明@Valid注解
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论