前言

在当下,不论是单机应用亦或者微服务应用,数据校验肯定是不可豁免的一部分工作,虽然当下流行的前端框架都会提供数据校验,但是作为前端的数据校验,想要越过是非常容易的,前端的数据校验更多的是对使用者的一个规范,而真正强制要求数据格式还得在后台进行数据校验

直接用if判断来做数据校验,即麻烦也不利于阅读,可谓是非常的不友好,作为10级强迫症的我,必然是不能用if来写数据校验的

好在是无敌的SpringBoot提供了解决方案

JSR303为一个java提供的数据校验标准,而hibernate对其进行了实现,SpringBoot对其进行了整合

安装

这里默认使用SpringBoot的开发环境

在SpringBoot的低版本的starter-parent中是直接整合了hibernate-validator这个依赖的(至少在2.1.1.RELEASE版本中还存在),这个依赖即是hibernate依据JSR303为标准的落地实现

但是在高版本的SpringBoot starter-parent依赖中(至少在2.3.5.RELEASE中)hibernate-validator被移除了

我们的安装方案有很多种,第一种最直接引用低版本的SpringBoot即可,如果我们想要引入高版本的SpringBoot时需要手动引入hibernate-validator,但是光引入这一个依赖有可能会出现兼容性问题,这里推荐使用SpringBoot高版本给出的解决方案即直接引入spring-boot-starter-validation依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

基础使用

对于使用这个组件的基础功能,还是非常简单的,简单来说就是给JavaBean的属性标注注解
这里就简要介绍常用的注解,需要了解更多可以去javax.validation.constraints和org.hibernate.validator.constraints的包下面去查找,这两个包第一个代表java的规范,而第二个为hibernate实现的和自己又新增的一些注解

常用注解

注解含义
@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(value)被注释的元素必须符合指定的正则表达式。
@Email被注释的元素必须是电子邮件地址
@Length被注释的字符串的大小必须在指定的范围内
@NotEmpty被注释的字符串必须非空
@Range被注释的元素必须在合适的范围内

使用

  1. 对JavaBean的属性标注注解
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")
@NotEmpty
private String firstLetter;

该实例标注firstLetter字段必须为单个的大写或小写的字母,且不得为空
2. 在Controller的方法中添加注解

@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
}

对于标注了@Valid的方法都会验证我们在bean中指定的规则

高级用法

上述基础用法,虽然简便但是有着一个不可豁免的缺陷,无法针对多场景来进行数据校验
比如有一个ID字段,在新增时我们希望它必须为空,而在修改时它又必须不为空,这些要求仅使用基础用法是无法达到的

这就需要我们使用Group功能来实现不同的场景效果的数据校验

规定分组,例如addGroup来针对数据添加的校验,UpdateGroup来针对数据修改的校验

用法

  1. 定义分组

其实这里的分组就是一个接口,这个接口只需要一个名称即可,内容完全不需要书写

public interface AddGroup {
}
public interface UpdateGroup {
}
...
  1. 给javabean属性的校验注解标注分组
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母", groups = {AddGroup.class, UpdateGroup.class})
@NotEmpty(groups = {AddGroup.class})
private String firstLetter;

这里就规定,在添加分组时不能为空,而添加分组和修改分组都必须满足为一个字母的正则规则

  1. 给Controller的方法中规定分组
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
}
@RequestMapping("/update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
    brandService.updateById(brand);
    return R.ok();
}

规定分组我们需要使用@Validated注解,注意这里也可以指定多个分组的class

自定义注解

在实际的开发中,默认提供的注解无论如何都是不够用的,这就需要我们可以自定义注解

自定义注解的步骤
这里就模拟创建一个ListValue注解,定义一个数组中的数据,而接口传入的数据必须被这个数组包含

  1. 创建注解类
@Documented
@Constraint(
        validatedBy = {ListValueConstraintValidator.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {

    String message() default "{com.atguigu.common.valid.annotation.ListValue.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    int[] vals() default {};

}

message和groups和payload都是规范中定义的,直接从之前默认的注解类中粘贴过来,最后的vals即为注解需要传入的数组

注意message中的default值,这个值是从配置文件中读取的,这个配置路径也是根据默认的注解类中的配置路径来设置的
这个message用来规定提示的信息,group即为我们之前用的分组

com.atguigu.common.valid.annotation.ListValue.message=必须提交指定的值
  1. 绑定解析类
    在注解类上标注的注解,可以从默认的注解类中直接粘贴,其中有一个@Constraint用来绑定解析我们定义的类的注解

创建一个类ListValueConstraintValidator实现ConstraintValidator接口重写其下的两个方法

ConstraintValidator接口的泛型,第一个值为注解类,第二个值为接口传递数据的值的类型,不同的类型可以引导入不同的解析类

public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {

    private Set<Integer> set = new HashSet<>();

    // 初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }

    }
    /**
     * 判断是否校验成功
     * @param integer 需要校验的值
     * @param constraintValidatorContext 环境信息
     * @return 是否通过
     */
    @Override
    public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
        return set.contains(integer);
    }
}

initialize方法也就是我们的注解初始化的方法,这里我们就把vals数组中的值读取存储到对象属性中,供下面的校验方法读取

isValid就是判断是否校验成功的方法,方法的返回值为true即为校验成功,为false即为校验失败
这里的判断很简单,直接判断传进入的interger是否被set集合包含即可

  1. 标注注解
@ListValue(vals = {0, 1}, message = "显示状态只能为0或者1", groups = {AddGroup.class, UpdateStatusGroup.class})
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;

这里的@ListValue即代表传入的showStatus值必须被vals数组包含

Q.E.D.


深至缄默,如云漂泊