Hibernate Validator实践之一 入门篇
在后台的业务逻辑中,对数据值的校验在各层都存在(展示层,业务层,数据访问层等),并且各层校验的规则又不尽相同,如下图所示
注:该图片来自于Hibernate Validator官网
在各层中重复的校验逻辑既导致了不必要的资源消耗,还使得逻辑不够单一(每层都夹杂着校验的逻辑),JSR 303 Bean Validation就是在这种背景下产生的一个数据验证的J2EE规范。而我们这篇文中将要介绍的Hibernate Validator则是JBoss社区开源的一个JSR 303 Bean Validation规范的优秀实践。
注:该图片来自于Hibernate Validator官网
下面我们以一个具体的列子讲述下如何在我们的工程中使用Hibernate Validator
首先我们定义了一个结构体Person,具体的定义如下public class Person {
@NotNull
private String name;
@Min(value = 1)
private int age;
@NotNull(groups = Intf1.class)
@Size(min = 1, max = 3, groups = Intf2.class)
private String group;
@GenderCase(value = GenderType.FEMALE)
private GenderType gender;
@Max(100)
public int getAge() {
return age;
}
@Max(50)
public int getAgeOther() {
return age + 2;
}
@Max(50)
public int getAgeOther(int num) {
return 51;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
其中该类中用到Max,Min,NotNull,Size等都是JSR 303中内置的约束条件(constraint),GenderCase是自定义的约束条件,这个在后面会介绍。
对Bean进行约束校验,首先需要先获得一个校验器实例ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
(一)如何对JSR 303 内置的约束条件进行校验
根据上面Person结构体的定义,我们看个简单的例子Person person = new Person(null, 20);
Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
assertEquals(1, constraintViolations.size());
System.out.println(constraintViolations);
在Person结构体定义中,name不可以为null,这里我们故意构造了一个name的null的Person实例,结果在控制台输入的结果如下:
[ConstraintViolationImpl{interpolatedMessage='不能为null', propertyPath=name, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.NotNull.message}'}]
从上面的例子可以看出,只需要在定义结构体中将JSR 303 内置的约束注解添加到对应的属性上,通过Validator实例的validate方法,如果返回的Set集合不为空,通过遍历集合便可知哪些属性的值非法。
Bean Validation 中的 constraint
表 1. Bean Validation 中内置的 constraint
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式
表 2. Hibernate Validator 附加的 constraint
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内
注:上面两个表格中的内容来自于这里,
如果有结构体嵌套,只需要在复合属性上通过Valid注解,则可以递归的进行校验。
(二)validateValue与validateProperty
通过javax.validation.Validator接口类的定义可知,校验的方法有三个,分别是validate,validateProperty,validateValue。其中validate会将所有的属性进行约束校验,而validateProperty是针对某一个具体的属性进行校验,validateValue是对具体的某一个属性和特定的值进行校验。具体看下面的两个例子
第一个例子:
Person person = new Person(null, 101);
Set<ConstraintViolation<Person>> constraintViolations = validator.validateProperty(person, "age");
assertEquals(1, constraintViolations.size());
System.out.println(constraintViolations);
根据上面的结构定义可以看出来在Person结构体中对age的约束有两个,一个是最小值为1,另一个是个getter方法上的约束最大不能超过100,执行上面的逻辑输出的结果为:
[ConstraintViolationImpl{interpolatedMessage='最大不能超过100', propertyPath=age, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.Max.message}'}]
可见validateProperty不光是对field的值进行校验,还会对getter方法也进行校验。
第二个例子:
Set<ConstraintViolation<Person>> constraintViolations = validator.validateValue(Person.class, "name", null);
assertEquals(1, constraintViolations.size());
System.out.println(constraintViolations);
第二个例子表示在执行validateValue时,给定一个结构体定义,field的名称,看该特定的值是否符合约束,执行的结果如下:
[ConstraintViolationImpl{interpolatedMessage='不能为null', propertyPath=name, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.NotNull.message}'}]
(三)约束条件的分组
在JSR 303 中定义了group的概念,用定义的接口类来标识,在上面的Person结构体定义的例子中可以看出有个group属性,该属性上有两个约束,分别是@NotNull(groups = Intf1.class) 和@Size(min = 1, max = 3, groups = Intf2.class)
下面通过一段代码执行的结果来看在参数校验中如何进行分组
Set<ConstraintViolation<Person>> constraintViolations = validator.validateValue(Person.class, "group", null, Intf1.class);
assertEquals(1, constraintViolations.size());
System.out.println("validate Intf1 |" + constraintViolations);
constraintViolations = validator.validateValue(Person.class, "group",null, Intf2.class);
assertEquals(0, constraintViolations.size());
System.out.println("validate Intf2 |" + constraintViolations);
constraintViolations = validator.validateValue(Person.class, "group","test", Intf2.class, Intf1.class);
assertEquals(1, constraintViolations.size());
System.out.println("validate Intf1&Intf2 |" + constraintViolations);
上段逻辑当group值为null时,首先用Intf1标识的约束条件进行校验,在用Intf2标识的约束条件进行校验。当group值为test时,同时用Intf1和Intf2标识的约束条件进行校验。执行的结果如下:
validate Intf1 | [ConstraintViolationImpl{interpolatedMessage='不能为null', propertyPath=group, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.NotNull.message}'}]
validate Intf2 | []
validate Intf1&Intf2 | [ConstraintViolationImpl{interpolatedMessage='个数必须在1和3之间', propertyPath=group, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.Size.message}'}]
从上面的例子可以看出,可以根据具体的业务选择不同的校验规则。
(四)约束条件的定制
对约束条件的定制,需要两步,第一步是定义约束的注解类:
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = GenderTypeValidator.class)
public @interface GenderCase {
String message() default "genderType invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
GenderType value() default GenderType.FEMALE;
}
这里需要关注的有三个点,第一个是@Constraint(validatedBy = GenderTypeValidator.class) 这里指定了下面将要说的约束校验的实现类,第二个是message属性,用于校验值非法是缺省的消息模版,第三个是注解对应的约束值value。在这个例子中,注解的约束值用的是一个枚举值表示男/女,缺省值为女
第二步是实现了javax.validation.ConstraintValidator<A extends Annotation, T>接口的约束校验实现类,上面说的validatedBy指向的就是该实现类,其中A表示自定义的注解类,T表示进行校验的字段的类型。具体的逻辑定于如下:
public class GenderTypeValidator implements
ConstraintValidator<GenderCase, GenderType> {
GenderType value;
@Override
public void initialize(GenderCase constraintAnnotation) {
value = constraintAnnotation.value();
}
@Override
public boolean isValid(GenderType obj, ConstraintValidatorContext context) {
if (value != null && obj != null && value != obj) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("gender should be " + value + "| the value is " + obj).addConstraintViolation();
return false;
} else {
return true;
}
}
}
在初始化方法中获取该注解的约束条件,在isValid方法中将传进来的obj的值与约束条件比较,如果满足则返回true表示校验通过,如果不满足则返回false,并将错误信息存储上上下文ConstraintValidatorContext中,最终反馈给调用者。
下面是调用该定制约束条件的逻辑:
Set<ConstraintViolation<Person>> constraintViolations = validator.validateValue(Person.class, "gender", GenderType.MALE);
assertEquals(1, constraintViolations.size());
System.out.println(constraintViolations);
执行的结果如下:
[ConstraintViolationImpl{interpolatedMessage='gender should be FEMALE| the value is MALE', propertyPath=gender, rootBeanClass=class hibernate.validator.Person, messageTemplate='gender should be FEMALE| the value is MALE'}]
Hibernate Validator实践之一 入门篇的更多相关文章
- Spring实践系列-入门篇(一)
本文主要介绍了在本地搭建并运行一个Spring应用,演示了Spring依赖注入的特性 1 环境搭建 1.1 Maven依赖 目前只用到依赖注入的功能,故以下三个包已满足使用. <properti ...
- 《Java从入门到放弃》入门篇:hibernate中的多表对应关系
hibernate中的对应关系其实就是数据库中表的对应关系, 就跟某些电影中的某些场景是一样一样滴. 比如可以是一男一女,还可以是一男多女, 更可以是多男一女,最后最后最后还可以是多男多女!!! 有些 ...
- spring boot(一):入门篇
构建微服务:Spring boot 入门篇 什么是spring boot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框 ...
- 1. web前端开发分享-css,js入门篇
关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人与人的教育背景与成长环境心理活动都有差别,但就别人的心得再结合自己的特点,然后探索适合自己的学 ...
- Unity3D大风暴之入门篇(海量教学视频版)
智画互动开发团队 编 ISBN 978-7-121-22242-9 2014年2月出版 定价:79.00元 328页 16开 编辑推荐 长达800分钟的高清教学视频,手把手教会初学者 数个开发案例 ...
- 非WEB项目中引入Hibernate Validator
前言: 网上一些朋友分享了关于hibernate-validator的使用方法,但是不是缺少关联库信息,就是提供的参考代码中缺少自定类. 希望我这一篇博客能够让你顺利的跑出预期的结果. 如果有错,可以 ...
- web前端开发分享-css,js入门篇(转)
转自:http://www.cnblogs.com/jikey/p/3600308.html 关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人 ...
- spring 入门篇
spring 入门篇 相对于Hibernate(冬眠),Spring(春天),具有更多的诗意与希望的感觉,是为了解决传统J2EE开发效率过低.开发商之间不统一.没有真正实现“写一次到处 ...
- PC游戏编程(入门篇)(前言写的很不错)
PC游戏编程(入门篇) 第一章 基石 1. 1 BOSS登场--GAF简介 第二章 2D图形程式初体验 2.l 饮水思源--第一个"游戏"程式 2.2 知其所以然一一2D图形学基础 ...
随机推荐
- CentOS之Shell基础
Linux默认的shell版本是bash. 我们所敲的命令都是有记录的:被保存在.bash_history文件中.只有当用户正常突出shell时,命令才会保存至.bash_history中. !!:连 ...
- 1-自己动手编写ArrayList集合
学习集合的原理,这是一个很简单的代码,没有索引下标越界判断,异常处理等,不够健壮,后面会继续更新完善代码..... 只是入门学习一下 package my; /*** * 自己动手编写一个ArrayL ...
- 命令提示符没办法登录MySQL
前几天在CMD命令下输入 MYSQL -UROOT -P 一直显示命令无效. 后来想知道原来是要在MYSQL的BIN(安装)目录下才能访问该命令,如果要在其他路径下使用sql命令,那么要配置MYSQ ...
- 网页的缓存Cache与控制
什么是缓存 Cache? 缓存位于客户端与服务器之间, 或者服务器与服务器之间.它决定是否保存所获资源的副本,以及如何使用副本,何时更新副本,这里所说的资源包括页面的HTML, 图片,文件等等. 使用 ...
- SQL with(unlock)与with(readpast)
所有Select加 With (NoLock)解决阻塞死锁,在查询语句中使用 NOLOCK 和 READPAST 处理一个数据库死锁的异常时候,其中一个建议就是使用 NOLOCK 或者 READPAS ...
- Solr——Windows下部署Solr6.6.0至Tomcat8.5.28(一)
一.window 环境 solr 6.6.3 下载地址 http://archive.apache.org/dist/lucene/solr/ jdk 1.8 tomcat 8.5 本机tomc ...
- Cmder - 在右键菜单添加"Cmder Here"
使用命令行或终端工具的时候都有一个让我们觉得麻烦的问题,就是需要cd很多目录达到目标位置.在可视化操作系统下面我们一般都是已经处在目标目录了,这时需要执行某些命令如: python test.py 现 ...
- lunix 集群,负载均衡,location
nginx location语法: location支持的语法优先级: 复制代码location匹配顺序 # www.s14hanju.com/1.location = / { 我是代码1} ...
- centos7 图形化界面
当你安装centos服务器版本的时候,系统默认是不会安装 CentOS 的图形界面程序的,比如:gnome或者kde, 那么如果你想在图形界面下工作的话,可以手动来安装CentOS Gnome GUI ...
- if 判断语句
if 条件:if语句块当条件成立的时候会执行if语句块, 如果条件不成立. 不执行语句块的内容 例: money = int(input("请输入你兜里的钱:")) # 300 i ...