5.参数校验Validation

一灰灰blogSpringBootWEB系列RequestValidation约 1461 字大约 5 分钟

业务开发的小伙伴总有那么几个无法逃避的点,如大段if/else,接口的参数校验等。接下来将介绍几种使用Validation-Api的方式,来实现参数校验,让我们的业务代码更简洁

I. 基本知识点

1. validation-api

参数校验有自己的一个规范JSR303, 它是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解。

java开发环境中,可以通过引入validation-api包的相关注解,来实现参数的条件限定

<dependency>
  <groupId>jakarta.validation</groupId>
  <artifactId>jakarta.validation-api</artifactId>
  <version>2.0.1</version>
  <scope>compile</scope>
</dependency>

请注意上面这个包只是定义,如果项目中单独的引入上面的这个包,并没有什么效果,我们通常选用hibernate-validator来作为具体的实现

<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>6.0.18.Final</version>
  <scope>compile</scope>
  <exclusions>
    <exclusion>
      <artifactId>validation-api</artifactId>
      <groupId>javax.validation</groupId>
    </exclusion>
  </exclusions>
</dependency>

下面给出一些常用的参数限定注解

注解描述
@AssertFalse被修饰的元素必须为 false
@AssertTrue被修饰的元素必须是true
@DecimalMax被修饰的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin同DecimalMax
@Digits被修饰的元素是数字
@Email被修饰的元素必须是邮箱格式
@Future将来的日期
@Max被修饰的元素必须是一个数字,其值必须小于等于指定的最大值
@Min被修饰的元素必须是一个数字,其值必须大于等于指定的最小值
@NotNull不能是Null
@Null元素是Null
@Past被修饰的元素必须是一个过去的日期
@Pattern被修饰的元素必须符合指定的正则表达式
@Size被修饰的元素长度
@Positive正数
@PositiveOrZero0 or 正数
@Negative负数
@NegativeOrZero0 or 负数

2. 项目搭建

接下来我们创建一个SpringBoot项目,用于后续的实例演示

我们采用IDEA + JDK1.8 进行项目开发

  • SpringBoot: 2.2.1.RELEASE

pom核心依赖如下

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

请注意 并没有显示的添加上一小节的两个依赖,因为已经集成在start包中了

II. 实例演示

接下来我们进入实例演示环节,会给出几种常见的使用case,以及如何扩展参数校验,使其支持自己定制化的参数校验规则

1. 校验失败抛异常

如果我们的参数校验失败,直接抛异常,可以说是最简单的使用方式了;首先我们创建一个简单ReqDo,并对参数进行一些必要的限定

@Data
public class ReqDo {

    @NotNull
    @Max(value = 100, message = "age不能超过100")
    @Min(value = 18, message = "age不能小于18")
    private Integer age;

    @NotBlank(message = "name不能为空")
    private String name;

    @NotBlank
    @Email(message = "email非法")
    private String email;

}

然后提供一个rest接口

@RestController
public class RestDemo {

    @GetMapping(path = "exception")
    public String exception(@Valid ReqDo reqDo) {
        return reqDo.toString();
    }
}

参数左边有一个@Valid注解,用于表示这个对象需要执行参数校验,如果校验失败,会抛400错误

演示如下

2. BindingResult

将校验失败的结果塞入BindingResult,避免直接返回400,这种方式只需要在方法参数中,加一个对象即可,通过它来获取所有的参数异常错误

@GetMapping(path = "bind")
public String bind(@Valid ReqDo reqDo, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return bindingResult.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList()).toString();
    }

    return reqDo.toString();
}

3. 手动校验

除了上面两个借助 @Valid 注解修饰,自动实现参数校验之外,我们还可以手动校验一个DO是否准确,下面给出一个简单的实例

@GetMapping(path = "manual")
public String manual(ReqDo reqDo) {
    Set<ConstraintViolation<ReqDo>> ans =
            Validation.buildDefaultValidatorFactory().getValidator().validate(reqDo, new Class[0]);
    if (!CollectionUtils.isEmpty(ans)) {
        return ans.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList()).toString();
    }

    return reqDo.toString();
}

4. 自定义参数校验

虽然JSR303规范中给出了一些常见的校验限定,但显示的业务场景千千万万,总会有覆盖不到的地方,比如最简单的手机号校验就没有,所以可扩展就很有必要了,接下来我们演示一下,自定义一个身份证校验的注解

首先定义注解 @IdCard

@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdCardValidator.class)
public @interface IdCard {
    String message() default "身份证号码不合法";

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

    Class<? extends Payload>[] payload() default {};
    
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    @interface List {

        IdCard[] value();
    }
}

上面的实现中,有几个需要注意的点

  • @Constraint 注解,指定校验器为 IdCardValidator, 即表示待有@IdCard直接的属性,由IdCardValidator来校验是否合乎规范
  • groups: 分组,主要用于不同场景下,校验方式不一样的case
    • 如新增数据时,主键id可以为空;更新数据时,主键id不能为空
  • payload: 知道这个具体干嘛用的老哥请留言指点一下

接下来完成身份证号的校验器IdCardValidator

这里直接借助hutool工具集中的cn.hutool.core.util.IdcardUtil#isValidCard来实现身份证有效性判断

public class IdCardValidator implements ConstraintValidator<IdCard, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }

        return IdcardUtil.isValidCard(value);
    }
}

然后将我们前面的ReqDo修改一下,新增一个身份证的字段

@IdCard
private String idCard;

再次访问测试(说明,图1中身份证号是随便填的,图2中的身份证号是http://sfz.uzuzuz.com/这个网站生成的,并不指代真实的某个小伙伴)

IdCard校验IdCard校验

II. 其他

0. 项目

Loading...