参数校验
在日常的项目开发中,我们为了数据的正确性,后端都会单独对数据进行校验,比如说用户信息中的年龄校验,用户名长度校验,用户性别校验等。
校验方式分类
我们常见的校验方式分为俩种,一种是使用校验类来进行校验,另外一种是使用spring validator或者hibernate validator。使用手动方式进行校验,虽然可以将常用逻辑的校验抽取成方式,但是代码中还是会存在很多校验方法的调用,显得不那么简洁。
使用validator
博主这里主要介绍一下如何使用hibernate validator来进行参数的校验,其中也包含了一些spring validator的内容。
pom依赖
因为我们使用了hibernate validator,我们需要先导入maven依赖。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>
常用注解
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 附加的 constraint
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
入参校验
一般在入参比较少的情况下,比如说只有一俩个参数的情况下,可以使用入参校验的方式。
而这种入参校验,我们需要在控制器上添加validated注解,然后在方法入参上添加校验注解,validated注解是spring validator的。
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
@GetMapping("/get")
public User getUser(@Min(1) int id){
return null;
}
}
模型校验
在控制器方法入参比较多的情况下,我们一般选用模型校验的方式。这时候我们需要在被校验的模型前面添加@Validated注解,在模型的属性上添加校验注解。
@PostMapping("/update")
public boolean updateUser(@Validated User user)
{
boolean success = false;
return success;
}
@Data
public class User {
@NotBlank(message = "用户名不能为空")
private String name;
@Min(value = 1)
private int age;
@Min(value = 1)
private int id;
}
嵌套校验
嵌套校验就是校验组合对象,组合对象在数据库中一般都对应为主从表,比如说订单主表 和 订单细表。这里我们方便起见,直接在之前的User对象中嵌套一个Book对象进行嵌套校验的测试。
@Data
public class User {
@NotBlank(message = "用户名不能为空")
private String name;
@Min(value = 1)
private int age;
@Min(value = 1)
private int id;
@Valid
Book book;
}
@Data
public class Book {
@Min(1)
private int id;
@Size(min=3, max = 10)
private String name;
}
分组校验
分组校验就是将校验规则进行分组,比如说user对象的新增来说,不需要校验id的范围,而对于user对象的更新来说,需要校验id的范围。如果不用分组校验的话,我们则可能需要创建俩个对象分别进行校验。
首先,我们先创建一个名为UpdateGroup.class,并且将其作用于id属性上,默认的校验分组为Default.class。
@Data
public class User {
@NotBlank(message = "用户名不能为空")
private String name;
@Min(value = 1)
private int age;
@Min(value = 1, groups = UpdateGroup.class)
private int id;
}
然后,我们需要修改校验模型前的@Validated注解。
@PostMapping("/add")
public boolean addUser(@Validated({UpdateGroup.class}) User user)
{
return true;
}
自定义校验
如果之前的常用注解,不能满足我们的要求,我们则需要自定义注解,我们这里使用自定义枚举类型的注解作为例子。
@Target({ElementType.FIELD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {EnumValueValidator.class})
public @interface EnumValue {
//默认错误消息
String message() default "必须为指定值";
int[] values() default {};
//分组
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
//指定多个时使用
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
EnumValue[] value();
}
}
//枚举校验类
public class EnumValueValidator implements ConstraintValidator<EnumValue, Object> {
private int[] values;
@Override
public void initialize(EnumValue constraintAnnotation) {
values = constraintAnnotation.values();
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
if(o instanceof Integer) {
Integer integer = (Integer) o;
for (int value : values) {
if(value == integer){
return true;
}
}
}
return false;
}
}
异常捕捉
在定义好我们的校验规则之后,如果校验失败,我们怎么将失败信息统格式后返回给前端?博主这里使用了全局异常捕捉的方式。
@ControllerAdvice
public class ExceptionIntercept {
//模型校验失败会产生这个异常
@ExceptionHandler(value = { ConstraintViolationException.class })
@ResponseBody
public BaseResponse handleException(ConstraintViolationException exception){
return BaseResponse.error(exception.getMessage());
}
//入参校验失败会产生这个异常
@ExceptionHandler(value = { BindException.class })
@ResponseBody
public BaseResponse handleException(BindException exception){
BindingResult bindingResult = exception.getBindingResult();
return getResponse(bindingResult);
}
//异常兜底
@ExceptionHandler(value = { Exception.class })
@ResponseBody
public BaseResponse handleException(Exception exception){
return BaseResponse.error(exception.getMessage());
}
private BaseResponse getResponse(BindingResult bindingResult){
StringBuilder errorInfo = new StringBuilder();
for (ObjectError allError : bindingResult.getAllErrors()) {
errorInfo.append(allError.toString());
}
return BaseResponse.error(errorInfo.toString());
}
}
进行了全局异常捕捉的配置之后,我们来简单的查看一下异常信息的返回。