springboot-使用validator进行参数校验

参数校验

在日常的项目开发中,我们为了数据的正确性,后端都会单独对数据进行校验,比如说用户信息中的年龄校验,用户名长度校验,用户性别校验等。

校验方式分类

我们常见的校验方式分为俩种,一种是使用校验类来进行校验,另外一种是使用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());
    }
}

进行了全局异常捕捉的配置之后,我们来简单的查看一下异常信息的返回。

查看异常信息

博主微信公众号

posted on 2020-08-19 23:32  幕友皎敖奔乾  阅读(347)  评论(0编辑  收藏  举报

导航