一、什么是JSR303
JSR是Java Specification Requests的缩写,意思是Java 规范提案。JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation。
二、为什么要使用JSR303
在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等。
为了避免这样的情况发生,最好是将验证逻辑与相应的域模型(通常是JavaBean, 那种只包含属性的类)进行绑定。
三、如何启用JSR303(重点)
3.1 引入依赖
3.2 给参数的对象类添加校验注解
3.3 在Controller 中校验的参数Bean前添加 @Valid
注意:如果在参数这里没有添加@Valid,则在上一步User类中添加的校验注解则不起效。
Controller 中需要校验的参数Bean前添加 @Valid 开启校验功能,紧跟在校验的Bean后添加一个BindingResult,BindingResult封装了前面Bean的校验结果。
测试如下:
![](https://pics0.baidu.com/feed/fd039245d688d43f14c5cbcd63827d1d0ff43bc7.jpeg?token=6a2a49024496ff4fbba14adc9c51291e)
四、如何处理校验异常
4.1 紧跟校验的Bean后的BindingResult
Controller 中需要校验的参数Bean前添加 @Valid 开启校验功能,紧跟在校验的Bean后添加一个BindingResult,BindingResult封装了前面Bean的校验结果。
![](https://pics5.baidu.com/feed/0d338744ebf81a4cae5658fecab6cf5f242da6c0.jpeg?token=d6d5dbe8b15d272ef2644dbd5f812141)
4.2 通过@RestControllerAdvice统一处理
参数校验不通过时,会抛出 BingBindException 异常,可以在统一异常处理中,做统一处理,这样就不用在每个需要参数校验的地方都用 BindingResult 获取校验结果了。
@RestControllerAdvice 注解相当于 @ResponseBody + @ControllerAdvice, @ControllerAdvice vs @RestControllerAdvice.
![](https://pics5.baidu.com/feed/54fbb2fb43166d22fff89c885bbfa6f19152d246.jpeg?token=569bd4318a67ae09d79c0e242c166f33)
五、补充知识
5.1 分组解决校验
新增和修改对于实体的校验规则是不同的,例如id是自增的时,新增时id要为空,修改则必须不为空;新增和修改,若用的恰好又是同一种实体,那就需要用到分组校验。
校验注解都有一个groups属性,可以将校验注解分组,我们看下@NotNull的源码:
![](https://pics6.baidu.com/feed/c995d143ad4bd113cdfde13247330b094bfb0524.jpeg?token=4e056c85f0596fbfcf04c0755d885a68)
从源码可以看出 groups 是一个Class<?>类型的数组,那么就可以创建一个Groups.(这里定义为接口,是为了面向接口编程)
![](https://pics6.baidu.com/feed/cb8065380cd7912382ba2aeab6a8f684b3b7807c.jpeg?token=aba2dfef9040e68a39f69394c55f275c)
给参数对象的校验注解添加分组(给校验字段后面加上groups字段,可以添加多个.class)
![](https://pics0.baidu.com/feed/0df431adcbef760967b2d79c30410cca7dd99e9e.jpeg?token=d02b0acbfdbd09083d80e72295b00fce)
Controller 中原先的@Valid不能指定分组 ,需要替换成@Validated。
一旦controller这里改为@Validated(分组),那么参数对象中只有添加了“对应的group .class信息”的才会校验。若不匹配.class/或者没有配置正确,均不生效。
![](https://pics3.baidu.com/feed/9213b07eca8065389d059a3e8a410e42ac348256.jpeg?token=77342262158164d871ac81d2782d2a72)
测试如下:
![](https://pics2.baidu.com/feed/8ad4b31c8701a18b226883e482b3a80e2938fe39.jpeg?token=a69513c8774b4d3f63f124eb6e678c35)
5.2 自定义校验注解
虽然JSR303和springboot-validator 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要 自定义校验注解。
例如User中的gender,用 1代表男 2代表女,我们自定义一个校验注解@ListValue,指定取值只能1和2。
创建约束规则
![](https://pics5.baidu.com/feed/267f9e2f070828385637ef58a30506074d08f1e2.jpeg?token=4f64e27dd5ba641b5c6979a753606bc7)
一个标注(annotation) 是通过@interface关键字来定义的. 这个标注中的属性是声明成类似方法的样式的. 根据Bean Validation API 规范的要求:
- message属性, 这个属性被用来定义默认得消息模版, 当这个约束条件被验证失败的时候,通过此属性来输出错误信息。
- groups 属性, 用于指定这个约束条件属于哪(些)个校验组. 这个的默认值必须是Class<?>类型数组。
- payload 属性, Bean Validation API 的使用者可以通过此属性来给约束条件指定严重级别. 这个属性并不被API自身所使用。除了这三个强制性要求的属性(message, groups 和 payload) 之外, 我们还添加了一个属性用来指定所要求的值. 此属性的名称vals在annotation的定义中比较特殊, 如果只有这个属性被赋值了的话, 那么, 在使用此annotation到时候可以忽略此属性名称.
另外, 我们还给这个annotation标注了一些元标注( meta annotatioins):
- @Target({ METHOD, FIELD, ANNOTATION_TYPE }): 表示此注解可以被用在方法, 字段或者annotation声明上。
- @Retention(RUNTIME): 表示这个标注信息是在运行期通过反射被读取的.
- @Constraint(validatedBy = ListValueConstraintValidator.class): 指明使用哪个校验器(类) 去校验使用了此标注的元素.
- @Documented: 表示在对使用了该注解的类进行javadoc操作到时候, 这个标注会被添加到javadoc当中.
创建约束校验器
![](https://pics0.baidu.com/feed/3bf33a87e950352a899d55664fdf54f4b3118b06.jpeg?token=5c821ccc35ce6b18daa910dc46823628)
ListValueConstraintValidator定义了两个泛型参数, 第一个是这个校验器所服务到标注类型(在我们的例子中即ListValue), 第二个这个校验器所支持到被校验元素的类型 (即Integer)。
如果一个约束标注支持多种类型的被校验元素的话, 那么需要为每个所支持的类型定义一个ConstraintValidator,并且注册到约束标注中。
这个验证器的实现就很平常了, initialize() 方法传进来一个所要验证的标注类型的实例, 在本例中, 我们通过此实例来获取其vals属性的值,并将其保存为Set集合中供下一步使用。
isValid()是实现真正的校验逻辑的地方, 判断一个给定的int对于@ListValue这个约束条件来说是否是合法的。
在参数对象中使用@ListValue注解。
![](https://pics1.baidu.com/feed/cdbf6c81800a19d87869aa6c2d662d8da71e4697.jpeg?token=f888fe79137c32ebe4ab7cca24fddcab)
测试如下:
![](https://pics4.baidu.com/feed/80cb39dbb6fd526635b623a6b584382dd5073601.jpeg?token=1ccb396b0abf497c4d11e23d7171fde4)