spring boot 参数校验 如何使用?

246 阅读5分钟

参数校验

用法

SpringBoot 中的 bean validation 是集成了hibernate-validatorJakarta Bean Validation,集成在 spring-context 包。

引入依赖

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

简单的校验

  • @Valid:常见用在方法、字段属性、参数上进行校验,不提供分组功能,但是支持字段属性的嵌套校验

  • @Validated:是spring提供的对@Valid的封装,常见用在方法参数上进行分组校验。

校验注解

如果使用比较少的话,你可能不太熟悉都有哪些注解校验,分别是校验什么功能,所以作为暖男的我在这里把所有校验注解都列出来~~

Hibernate Validator官方手册

注解验证的数据类型含义
@Valid任何非原子类型被注释的元素是一个对象,需要检查此对象的所有字段值;指定递归验证关联的对象;如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证
@NotNull任意类型验证字段不为 null
@NotBlankCharSequence子类型(CharBuffer、String、StringBuffer、StringBuilder)验证注解的元素值不为空(不为null、去除首尾空格后长度不为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首尾空格
@NotEmptyCharSequence子类型、Collection、Map、数组验证字段不为null且不为空,常用于校验集合元素不为空
@AssertTrueBoolean,boolean被注释的元素必须为 true
@AssertFalseBoolean,boolean被注释的元素必须为 false
@MinBigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max和@Min要求一样被注释的元素必须是一个数字,其值必须小于等于指定的最大值,与 @Max 类似,不同的是它限定值可以带小数,一般用于 double 和 Bigdecimal 类型
@DecimalMin和@Min要求一样被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax和@Min要求一样被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(min, max)字符串、Collection、Map、数组等验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小
@Length(min, max)CharSequence子类型被注释的字符串的大小必须在指定的范围内
@Range(min, max)BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型验证数字类型字段值在最小值和最大值之间
@Digits(integer=整数, fraction=小数)和@Min要求一样被注释的元素必须是一个数字,验证注解的元素值的整数位数和小数位数上限(必须在可接受的范围内)
@Pastjava.util.Date,java.util.Calendar;Joda Time类库的日期类型被注释的元素必须是一个过去的日期(元素值(日期类型)比当前时间早)
@Future与@Past要求一样被注释的元素必须是一个将来的日期(元素值(日期类型)比当前时间晚)
@PastOrPresent验证日期类型字段值比当前时间早或者是当前日期
@FutureOrPresent验证日期类型字段值比当前时间晚或者是当前日期
@PositiveOrZero校验必须是正数或 0
@NegativeOrZero校验必须是负数或 0
@Negative校验必须是负数
@Positive校验必须是正数
@Pattern(regexp,flag)CharSequence的子类型被注释的元素必须符合指定的正则表达式
@EmailCharSequence的子类型验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式
@URL(protocol,host, port,regexp, flags)CharSequence的子类型被注释的字符串必须是一个有效的url
@UniqueElementsCollection验证所提供的Collection中的每个对象都是唯一的,即在集合中找不到2个相等的元素。
@ScriptAssert(lang= ,script=)业务类校验复杂的业务逻辑
@CreditCardNumber

分组校验

简单使用

新增用户信息和修改用户信息所需要验证的字段是不同的.


public interface AppUserVaildC extends Default {
}

public interface AppUserVaildU extends Default {
}

Model中

@Range(min = 0,max = 100,message = "年龄必须在[0,100]",groups={Default.class})
    /**年龄*/
private Integer age;
@Range(min = 0,max = 2,message = "性别必须在[0,2]",groups = {AppUserVaildC.class})
    /**性别 0:未知;1:男;2:女*/
private Integer sex;

Controller中使用

@PostMapping("save")
public void v1(@RequestBody @Validated({AppUserVaildC.class, AppUserVaildU.class}) AppUser appUser,BindingResult result){
      if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
}

普通使用

@Autowired
private Validator validator;

@PostMapping
public void apply(@RequestBody @Validated AppInvoiceApplyRequest request) {
    Set<ConstraintViolation<FundApplyVerifyRequest>> constraintViolations = null;
    if (isCorporation) { //企业开票
        constraintViolations = validator.validate(request,AppInvoiceApplyRequest.CompanyInvoiceGroup.class);
    } else { //个人开票
        constraintViolations = validator.validate(request,AppInvoiceApplyRequest.PersonalCompanyInvoiceGroup.class);
    }
  
    for (ConstraintViolation<AppUser> model : violationSet) {
        System.out.println(model.getMessage());
    }
    if (CollectionUtils.isNotEmpty(constraintViolations)) { //如果没有通过校验,抛出异常
        throw new ConstraintViolationException(constraintViolations);
    }
    appInvoiceService.save(request);
}

组序列

@GroupSequence,除了按组指定是否验证之外,还可以指定组的验证顺序,前面组验证不通过的,后面组不进行验证.

@GroupSequence({First.class, Second.class})
public interface Group {
}

Controller中使用

@PostMapping("save")
public void v1(@RequestBody @Validated({Group.class}) AppUser appUser){
      
}

自己定义注解

下面是一个自定义大小写的验证

public enum CaseMode {
    UPPER,
    LOWER;
}


// 首先定义一个校验注解
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)	//用哪个校验器校验
@Documented
public @interface CheckCase {
    String message() default "";

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

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

    CaseMode value();
}

// 校验逻辑,我们只需要去实现 ConstraintValidator 这个接口重写 isValid 方法即可
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
    private CaseMode caseMode;
  
    @Override
    public void initialize(CheckCase checkCase) {
        this.caseMode = checkCase.value();
    }

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

        if (caseMode == CaseMode.UPPER) {
            return s.equals(s.toUpperCase());
        } else {
            return s.equals(s.toLowerCase());
        }
    }
}

使用

@CheckCase(value = CaseMode.LOWER ,message = "年必须是小写",groups={Default.class})
private String loginName;