likes
comments
collection
share

springboot如何校验参数

作者站长头像
站长
· 阅读数 16

前言

在日常开发中,我们少不了需要对前端的请求参数的验证。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) 时都需要验证。

自定义验证

结合上一篇我们介绍的枚举参数自动绑定 juejin.cn/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?这中间其实涉及到了太多的商业上的博弈。有兴趣的大家可以自己去了解下,有时间单独出一篇介绍。