优雅的参数校验(JSR-303的实现Hibernate-Validator)


一、背景

在我们平时开发中,经常会对前台传给我们的参数进行校验,如:

@GetMapping("test")
public String test(String id) {
    if (id != null && id.trim() != "") {
        throw new RuntimeException("id不能为空");
    }
    //TODO some method
    return "haha";
}

单独一个还行,但要说来个十个八个需要校验的,而且还有邮箱什么的格式验证,又繁琐又重复。我心态就炸了。。。

那么怎么才能简单灵活而又不重复且优雅的解决这个问题呢?

二、方案

其实Java为我们提供了很多的Java规范提案(JSR),如我们接下来要使用的JSR-303。

JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,用来对参数的校验。但是这只是一个规范,我们不能直接使用。在规范的实现中我们常用的就是:Hibernate-Validator。接下来我们来体验一下。

三、Hibernate-Validator配合注解使用

1)引入依赖

<!-- springboot下web启动器已经依赖了这个包 -->
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.0.Final</version>
</dependency>

2)使用

注解分为两类(先介绍一个简单实用,更多注解介绍见:4)注解):

一种,加在字段上如:

@Data
public class TestBean {
    private int id;

    /**
     * 这个注解保证name不为null且不为空格字符串,否则抛出异常
     */
    @NotBlank(message = "name不能为空")
    private String name;
}

这样就可以校验了吗?No,这样还不行哦,还要配合第二种注解@Valid/@Validated,加在接口参数对象上,如:

/**
 * 这里的@Valid也可以换成@Validated效果是一样的
 * @param testBean
 * @return
 */
@GetMapping("test")
public String test(@Valid TestBean testBean) {
    //TODO 自己的逻辑
    return "success";
}

如果这里的name为null或者空格字符串(如:"")那么就会抛出异常:org.springframework.validation.BindException

Field error in object 'testBean' on field 'name': rejected value []; codes [NotBlank.testBean.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testBean.name,name]; arguments []; default message [name]]; default message [name不能为空]]

3)场景

下面总结几种能够起到校验作用的使用场景:

  • 接口方法形参采用JavaBean形式(就像上面的那个例子一样)如:
@GetMapping("test")
public String test(@Valid TestBean testBean) {
    //TODO 自己的逻辑
    return "success";
}
  1. 这里的接口形式适合GET或Content-Type设置成application/x-www-form-urlencoded的Post的接口;
  2. 这里的@Valid也可以换成@Validated效果是一样的;
  • 接口方法形参采用JavaBean形式(@RequestBody)如:
@GetMapping("test")
public String test(@Valid @RequestBody TestBean testBean) {
    //TODO 自己的逻辑
    return "success";
}
  1. 这里的接口形式适合Content-Type设置成application/json的Post的接口;
  2. 这里的@Valid也可以换成@Validated效果是一样的;
  • 接口方法形参采用非JavaBean形式,如:
@GetMapping("test")
public String test(@NotBlank(message = "name不能为空")String name) {
    //TODO 自己的逻辑
    return "success";
}

注意这种形式在参数前使用@Valid/@Validated经测试起不到校验作用,这种情况只能只能使用@Validated并注释在此方法所在的Controller类上才能起到作用。

4)注解

下面介绍下常用注解的如下:

  • 加载需要校验的字段上的(javax.validation提供)

注解

含义(用法)

@AssertTrue

用于boolean字段,该字段只能为true

@AssertFalse

该字段的值只能为false

@CreditCardNumber

对信用卡号进行一个大致的验证

@DecimalMax(value)

只能小于或等于该值(必须是数字)

@DecimalMin(value)

只能大于或等于该值(必须是数字)

@Digits(integer=,fraction=)

验证是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。

@Email

检查是否是一个有效的email地址

@Future

检查该字段的日期是否是属于将来的日期

@Length(min=,max=)

检查所属的字段的长度是否在min和max之间(只能用于字符串)

@Max(value)

该字段的值只能小于或等于该值(必须是数字)

@Min(value)

该字段的值只能大于或等于该值(必须是数字)

@NotNull

不能为null(字符串)

@NotBlank

trim()之后不能为empty(字符串)

@NotEmpty

不能为null和empty,这里的空是指空字符串也可集合

@Length(min=,max=)

被注释的字符串的大小必须在指定的范围内

@Null

被注释的元素必须为 null

@Past

检查该字段的日期是在过去

@Pattern(regex=,flag=)

被注释的元素必须符合指定的正则表达式

@Range(min=,max=)

被注释的元素必须在合适的范围内

@Size(min=, max=)

检查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等

@URL(protocol=,host,port)

检查是否是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件

上面注解都包含message这个属性,在满足条件时异常输出的提醒就是message设置的值

  • 要想上面的字段必须在参数对象上加上

注解

注意

@Valid

javax.validation提供

@Validated

spring提供(在上面的基础上做了扩展)

下面讲下两者的区别:

  1. @Validated:作用在类上、方法、参数
  2. @Valid:方法、构造方法、参数、成员属性上
  3. @Validated支持分组:在接口形参上加上@Validated({A.class})(A是定义的一个分接口)则只会对相对应的XX类型的group起校验作用其他的分组则不会:


  1. @Valid可做嵌套:
@Data
public class TestBean {
    /**
     * 姓名 notblank
     */
    @NotBlank(message = "name不能为空")
    private String name;
    @Valid
    private List<TestBean2> beans; 
}

@Data
public class TestBean2 {
    @NotNull(message = "age不能为null")
    private Integer age;
}

在接口形参上要校验TestBean且还要校验TestBean下对象成员变量TestBean2下的成员变量,那就只能使用@Valid标注在TestBean下成员变量beans上,才能实现嵌套校验。

四、最后

至此你就可以优雅灵活的完成对参数的校验了。

那校验过未通过抛出异常呢,异常信息那么长怎么处理呢,不要着急,敬请期待下期,《统一异常处理》。

更多资源:其实是白羊(https://gitee.com/zhanglinlu)