前言:最近项目里面用到了hibernate-validator做接口的参数校验,第一感觉很好用,但是返回的提示还不够友好,这里全面了解一下这个项目的用法和功能。
此工具的中文文档我有传到我的下载中 可以在这里下载 https://download.csdn.net/download/zhanjunpeng01/11217481
什么是Hibernate validator呢?它是基于JSR-303接口的标准,在数据模型上添加需要校验的方式(基于注解或者XML扩展),实现对数据模型的验证框架。
为什么要用?1. 将数据验证从业务代码中抽离,降低了业务代码的复杂性。2. 可以在任意地方对实体进行校验,是否是符合标准的数据。 3. 可以减少很大的工作量,尤其是在数据模型比较庞大的地方。
首先可以根据官方的maven archetype创建一个简单的项目,了解一下这个工具的用法
mvn archetype:generate -DarchetypeGroupId=org.hibernate \
-DarchetypeArtifactId=hibernate-validator-quickstart-archetype \
-DarchetypeVersion=4.2.0.Final \
-DarchetypeRepository=http://repository.jboss.org/nexus/content/
groups/public-jboss/ \
-DgroupId=com.mycompany \
-DartifactId=hv-quickstart
也可以直接在idea里面创建maven项目,输入上面的参数创建出项目:
创建出来的工程即包含了一些基础代码和测试类
如果创建不出来,可以在maven里面加一下阿里云的镜像试试
nexus-aliyun
*
Nexus aliyun
http://maven.aliyun.com/nexus/content/groups/public
基础用法先参考代码中的Car类,以及CarTest类
给javabean添加校验注解有三种方式
1. 添加到类的字段上(field level)
2. 添加到字段的get方法上(property level)
3. 添加到类上(class level)
注解到字段上和属性上的区别好像不太大,官方也比较推荐注解到字段上,
而类级别的验证,则是由于某些场景,需要多个字段同时满足条件的时候比较有用。
在父类中定义的约束对子类一样有效,就像定义在子类上一样的效果
@Valid用于级联校验,如果一个类中引入了另外一个类,比如 ClassRoom类中有 List
Validator接口是HV(Hibernate Validator)中对实体进行校验操作的最重要的一个接口。对校验的功能进行了定义
下面是获取Validator默认实现的代码
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
这个接口提供了三个对实体校验的方法:大致的使用方法为
Set
返回的violations结果中包含了所有不符合规则定义的错误集合,可以接收0个或多个校验组作为参数
三个校验方法分别为:
1. 对对象的校验: validate(obj)
2. 对字段的校验: validateProperty(obj, fieldName)
3. 校验某个字段使用某个值是否是合法的:validateValue(obj, fieldName, value)
返回的ConstraintViolation集合的主要方法如下:
每个验证失败的消息提示都来自于一个消息模板,声明一个注解校验约束的时候,可以通过设置message属性来更改这个模板。模板中还可以使用占位符来设置不确定的内容,如下:
同时,你配置的MessageInterpolator可以用来解析这个模板,系统默认的MessageInterpolator会优先查找类路径下找名称
为ValidationMessages.properties的ResourceBundle, 如果能匹配不成功,则去HV自带的资源文件位于/org/hibernate/
validator/ValidationMessages.properties的ResourceBundle,如下图:
由此可见,HV支持国际化也是很方便的,正好可以在项目中用到,后面我将会在代码中测试一下这个功能。
校验组是校验注解中的一个属性groups,一个实体内的所有字段,可能在不同的情况下有不同的校验需求,下面先简单描述一下,详细的代码可以看官方文档第2.3章节进行了解。
比如,一个商品实体里面有商品名称,库存,是否已审核三个字段,其中商品是否能上架需要库存>0, 且是已审核的,而简单的保存商品信息则不需要检查。那么首先我们需要先定义一个接口表示一个组,这里我定义了一个空的接口OnSaleCheck.java ,使用接口作为组的定义而不是字符串是因为接口可以做到类型安全,而且对重构也比较友好,
定义上架检查分组的接口类:
public interface OnSaleCheck {
}
在实体类中使用分组,注意看属性上面的注解属性groups = {OnSaleCheck.class}
public class Goods {
@NotNull
private String goodsName;
@NotNull
@Min(value = 1, groups = {OnSaleCheck.class})
private Integer stockNum;
@AssertTrue(groups = {OnSaleCheck.class})
private boolean approvedPass;
//get、set方法省略...
}
在测试类中使用:
@Test
public void goodsOnSaleCheck(){
Goods goods = new Goods();
goods.setGoodsName("测试");
goods.setStockNum(0);
goods.setApprovedPass(true);
assertEquals(1, validator.validate(goods, OnSaleCheck.class).size());
assertEquals(0, validator.validate(goods).size());
goods.setGoodsName(null);
assertEquals(2, validator.validate(goods, OnSaleCheck.class, Default.class).size());
}
测试通过,最后两行的代码说明,使用了OnSaleCheck.class分组的校验,才会对该分组下面的属性做校验
需要注意的是,其实每个校验注解都会有一个分组,如果没有明确定义,则会使默认分组:javax.validation.groups.Default接口
而validate方法,可以接收多个校验组作为参数,如果你想校验所有的属性,那么在validate()方法再加上一个参数Default.class就可以啦
另外需要注意的是,如果是在validate方法中传入多个校验组,这些校验组的校验是没有顺序的,然而有些时候我们希望能够按照一定的顺序进行校验,那么需要再定义一个新的接口,并且用@GroupSequence注解这个接口。
但是,用@GroupSequence注解的接口,聚合了多个校验组,在发现第一个不符合条件的校验以后,就会返回,并不会再继续对后面的其它条件进行校验。
这里用校验组组序列(GroupSequence)做一个例子
定义一个新的接口 OnSaleCheckAll ,我们希望先校验基础信息,再校验是否可以上架
/**
* Describe 先校验商品基础信息,再校验是否可以上架
* Created by zhanjp on 2019/6/1
*/
@GroupSequence({Default.class, OnSaleCheck.class})
public interface OnSaleCheckAll {
}
测试:
这里我们可以发现,校验序列组的检查再发现第一个错误之后就返回了。
还有一个需要注意的地方,校验序列组不可包含循环引用,什么意思呢?就是校验序列组A中包含了B,而校验序列组B中又包含了A,这种情况是会报错的。
如果想在子类中重新去定义校验序列组,直接在子类上添加@GroupSequence注解就好了,不过不可再次在@GroupSequence中加入Default.class, 而是用"子类.class"替代,感觉这个不是常用,这里不再描述,感兴趣的可以看2.3.2节
@GroupSequenceProvider注解 是官方提供的一个非标准重新定义校验序列组的功能,相较于在子类上添加@GroupSequence改变父类的校验序列组,使用@GroupSequenceProvider可以按照一些条件动态添加校验组。
校验规则分为三种,1. java默认支持的规则(注解位于javax.validation.constraints) 2. HV自己实现的规则 (位于org.hibernate.validator.constraints.impl),3. 客户端自定义(自己开发)
java支持的注解和HV支持的注解,直接可以在开发包中看,这里不再描述
主要介绍自定义校验规则
定义一个自定义的校验规则需要三个步骤:
1. 创建约束注解
2. 实现一个验证器
3. 定义默认的验证错误消息
按照文档上的例子来实现一下:创建一个校验字符串大小写的自定义校验规则
step1: