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图形学基础 ...
随机推荐
- 2018/11/5 每日分析-test
郑醇1901,M30向上一笔中,只是看起来不太值得做,主要因为现在30分钟向上一笔空间无法判定,未必能上去(M5中枢如果向上突破并且不背驰才可能有机会:如果直接下去或者向上后背驰,那么这里就只是一个M ...
- 03-String常用方法
1.获取方法 /* * 编辑:刘诗华 int length() 获取字符串的长度 char charAt(int index) 获取特定位置的字符 (角标越界) int indexOf(String ...
- Linux下Mysql的odbc配置
在安装配置之前,需要先大概了解一下MyODBC的架构. MyODBC体系结构建立在5个组件上,如下图所示: Driver Manager: 负责管理应用程序和驱动程序间的通信,主要功能包括:解析DSN ...
- python操作符与流程控制
操作符: 算术运算: + - * / % // ** 逻辑运算:and or not 身份运算: is not is 不可变数据类型:数字 字符串 字典key 可变数据 ...
- 为什么使能RPS/RFS, 或者RSS/网卡多队列后,QPS反而下降?
http://laoar.github.io/blog/2017/05/07/rps/ TL;DR RPS 即receive side steering,利用网卡的多队列特性,将每个核分别跟网卡的一个 ...
- vue的v-for数组和对象
v-for="(item,index) of hot" //数组遍历 v-for="(item,key,index) of cities" //对象遍历 //k ...
- python 27 获取时区转换后的时间
python3的datetime有timezone属性,这里介绍python2.7环境下,获取时区转换后的时间. 利用第三方插件,pytz,没有安装的话安装一下. #!/usr/bin/env pyt ...
- 转载:centos安装redis
转载自:https://www.cnblogs.com/renzhicai/p/7773080.html CentOS下Redis的安装 [TOC] 前言 安装Redis需要知道自己需要哪个版本,有针 ...
- 1-hadoop安装、ssh、节点退役与服役
1.准备 四台虚拟机 ①卸载openjdk ②安装jdk 2. 配置静态ip: ip : 每个机器在internet上的唯一标识 子网掩码: 必须结合IP地址一起使用,将某个IP地址划分成网络地址和主 ...
- MYSQL--表分区、查看分区
一. mysql分区简介 数据库分区 数据库分区是一种物理数据库设计技术.虽然分区技术可以实现很多效果,但其主要目的是为了在特定的SQL操作中减少数据读写的总量以缩减sql语句的响应时 ...