前语

在日常开发中,咱们少不了需求对前端的恳求参数的验证。Spring供给了多种办法来完成恳求参数的验证。咱们一同了解一下吧。

注解

设置依赖

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

注解

以下是 validation-api中供给的可用的注解列表

注解 含义
@Null 验证目标必须为 null
@NotNull 验证目标不为 null
@NotBlank 验证字符串不能为空null或””,只能用于字符串验证
@NotEmpty 验证目标不得为空,可用于Map和数组
@Pattern 验证字符串是否满意正则表达式
@AssertTrue 验证 boolean 类型值为 true
@AssertFalse 验证 boolean 类型值为 false
@Min(value) 验证数字的巨细是否大于等于指定的值
@Max(value) 验证数字的巨细是否小于等于指定的值
@DecimalMin(value) 验证数字的巨细是否大于等于指定的值,小数存在精度
@DecimalMax(value) 验证数字的巨细是否小于等于指定的值,小数存在精度
@Size(max, min) 验证目标(字符串、集合、数组)长度是否在指定规模之内
@Digits(integer, fraction) 验证数字是否契合指定格局
@Pattern(value) 验证字符串是否契合正则表达式的规则
@ParameterScriptAssert 能够在办法参数级别上履行脚本验证,以验证传递给办法的参数是否契合指定的条件。该注解能够与 JSR 223 兼容的脚本引擎一同运用,例如 JavaScript、Groovy、Python 等。- script:脚本表达式,用于履行参数验证。该表达式应该回来一个 boolean 类型的值,表示验证是否通过。- lang:脚本语言的称号,默以为 “groovy”。
@Email 验证字符串是否契合电子邮件地址的格局。
@Future 验证一个日期或时刻是否在当时时刻之后。
@FutureOrPresent 验证一个日期或时刻是否在当时时刻之后或等于当时时刻。
@past 验证一个日期或时刻是否在当时时刻之前。
@PastOrPresent 验证一个日期或时刻是否在当时时刻之前或等于当时时刻。
@Positive 验证数字是否是正整数,0无效
@PositiveOrZero 验证数字是否是正整数
@Negative 验证数字是否是负整数,0无效
@NegativeOrZero 验证数字是否是负整数

以上大致便是validation-api默许供给的参数验证注解。示例如下:

springboot如何校验参数

别的需求注意的是,在每个注解中基本上都一个分组特点:Class<?>[] groups() default { };

其作用便是对目标特点分组,用于在不同的场景下支撑不同的参数验证。咱们来举一个列子:

比如咱们新增和更新数据的操作一般都用同一个目标来接纳前端传入参数。那么新增时的Id可能为空,而编辑时的Id不能为空,咱们对参数的验证规则是不一样的。那么怎样完成呢?这里就需求用到咱们的groups()了。代码如下:

    @RequestMapping("create")
    @ResponseBody
    public String create(@RequestBody @Validated(Create.class) User user) {
        return "create success";
    }
    @RequestMapping("update")
    @ResponseBody
    public String update(@RequestBody @Validated(Update.class) User user) {
        return "update success";
    }
    @Data
    public static class User {
        @NotBlank(message = "id不能为空", groups = {Update.class})
        private String id;
        @NotNull(message = "用户名不能为空", groups = {Create.class, Update.class})
        private String username;
        @NotNull(message = "密码不能为空")
        private String password;
    }
    public interface Create {
    }
    public interface Update {
    }

根据需求,咱们在 User 的 id 特点上标记上 groups = {Update.class},在 username 特点上标记 groups = {Create.class, Update.class}。则表明咱们对id只在 @Validated(Update.class) 时验证id的值;username 则在 @Validated(Update.class) 或者 @Validated(Create.class) 时都需求验证。

自界说验证

结合上一篇咱们介绍的枚举参数主动绑定 /post/722370…。现在咱们来完成一个怎样验证枚举的数据是否合规。

首要,界说一个自界说注解,例如 @ValidEnum:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidEnumValidator.class)
public @interface ValidEnum {
    String message() default "无效的枚举值";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    Class<? extends Enum<?>> value();
}

@ValidEnum 注解用于验证一个枚举类型的值是否在指定的枚举类型中,运用 ValidEnumValidator 类对其进行验证。

接下来,界说一个 ValidEnumValidator 类,完成对 @ValidEnum 注解的验证逻辑:

public class ValidEnumValidator implements ConstraintValidator<ValidEnum, Object> {
    private Set<String> validValues = new HashSet<>();
    @Override
    public void initialize(ValidEnum constraintAnnotation) {
        Class<? extends Enum<?>> enumClass = constraintAnnotation.value();
        Enum<?>[] enumValues = enumClass.getEnumConstants();
        for (Enum<?> enumValue : enumValues) {
            validValues.add(enumValue.name());
        }
    }
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        return validValues.contains(value.toString());
    }
}

ValidEnumValidator 类完成了 ConstraintValidator 接口,并重写了 initialize() 和 isValid() 两个办法。initialize() 办法用于初始化验证器,获取枚举类型的所有枚举值;isValid() 办法用于完成验证逻辑,判别枚举值是否在指定的枚举类型中。

最后,在运用 @Validated 注解的 Controller 中,对恳求参数运用自界说的 @ValidEnum 注解进行验证

@RestController
@Validated
public class UserController {
    @GetMapping("/user")
    public void getUser(@RequestParam @ValidEnum(value = Gender.class) String gender) {
        // 处理恳求
    }
}

@ValidEnum 注解用于验证 gender 参数是否在 Gender 枚举类型中。如果 gender 参数的值不在 Gender 枚举类型中,则会抛出 ConstraintViolationException 反常,并带有指定的错误消息。

需求注意的是,自界说的 Bean Validation 注解需求运用 @Constraint 注解进行标示,并指定对应的验证器类。

在验证器类中,需求完成 ConstraintValidator 接口,并完成 initialize() 和 isValid() 办法。在运用自界说注解进行参数验证时,需求在对应的 Controller 中运用 @Validated 注解进行标示。

风趣的历史

springboot如何校验参数

我们能够看到spring-boot-starter-validation 中依赖的包是 jakarta.validation-api,其命名空间归于jakarta。jakarta.validation-api 完成的标准依旧是JSR380,了解的小伙伴可能知道其实以前用的是 javax.validation-api。可是为什么现在会是jakarta.validation-api 呢,而不是 javax.validation-api?这中心其实涉及到了太多的商业上的博弈。有兴趣的我们能够自己去了解下,有时刻单独出一篇介绍。