在后台的业务逻辑中,对数据值的校验在各层都存在(展示层,业务层,数据访问层等),并且各层校验的规则又不尽相同,如下图所示

注:该图片来自于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实践之一 入门篇的更多相关文章

  1. Spring实践系列-入门篇(一)

    本文主要介绍了在本地搭建并运行一个Spring应用,演示了Spring依赖注入的特性 1 环境搭建 1.1 Maven依赖 目前只用到依赖注入的功能,故以下三个包已满足使用. <properti ...

  2. 《Java从入门到放弃》入门篇:hibernate中的多表对应关系

    hibernate中的对应关系其实就是数据库中表的对应关系, 就跟某些电影中的某些场景是一样一样滴. 比如可以是一男一女,还可以是一男多女, 更可以是多男一女,最后最后最后还可以是多男多女!!! 有些 ...

  3. spring boot(一):入门篇

    构建微服务:Spring boot 入门篇 什么是spring boot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框 ...

  4. 1. web前端开发分享-css,js入门篇

    关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人与人的教育背景与成长环境心理活动都有差别,但就别人的心得再结合自己的特点,然后探索适合自己的学 ...

  5. Unity3D大风暴之入门篇(海量教学视频版)

    智画互动开发团队 编   ISBN 978-7-121-22242-9 2014年2月出版 定价:79.00元 328页 16开 编辑推荐 长达800分钟的高清教学视频,手把手教会初学者 数个开发案例 ...

  6. 非WEB项目中引入Hibernate Validator

    前言: 网上一些朋友分享了关于hibernate-validator的使用方法,但是不是缺少关联库信息,就是提供的参考代码中缺少自定类. 希望我这一篇博客能够让你顺利的跑出预期的结果. 如果有错,可以 ...

  7. web前端开发分享-css,js入门篇(转)

    转自:http://www.cnblogs.com/jikey/p/3600308.html 关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人 ...

  8. spring 入门篇

    spring 入门篇         相对于Hibernate(冬眠),Spring(春天),具有更多的诗意与希望的感觉,是为了解决传统J2EE开发效率过低.开发商之间不统一.没有真正实现“写一次到处 ...

  9. PC游戏编程(入门篇)(前言写的很不错)

    PC游戏编程(入门篇) 第一章 基石 1. 1 BOSS登场--GAF简介 第二章 2D图形程式初体验 2.l 饮水思源--第一个"游戏"程式 2.2 知其所以然一一2D图形学基础 ...

随机推荐

  1. CentOS之Shell基础

    Linux默认的shell版本是bash. 我们所敲的命令都是有记录的:被保存在.bash_history文件中.只有当用户正常突出shell时,命令才会保存至.bash_history中. !!:连 ...

  2. 1-自己动手编写ArrayList集合

    学习集合的原理,这是一个很简单的代码,没有索引下标越界判断,异常处理等,不够健壮,后面会继续更新完善代码..... 只是入门学习一下 package my; /*** * 自己动手编写一个ArrayL ...

  3. 命令提示符没办法登录MySQL

    前几天在CMD命令下输入  MYSQL -UROOT -P 一直显示命令无效. 后来想知道原来是要在MYSQL的BIN(安装)目录下才能访问该命令,如果要在其他路径下使用sql命令,那么要配置MYSQ ...

  4. 网页的缓存Cache与控制

    什么是缓存 Cache? 缓存位于客户端与服务器之间, 或者服务器与服务器之间.它决定是否保存所获资源的副本,以及如何使用副本,何时更新副本,这里所说的资源包括页面的HTML, 图片,文件等等. 使用 ...

  5. SQL with(unlock)与with(readpast)

    所有Select加 With (NoLock)解决阻塞死锁,在查询语句中使用 NOLOCK 和 READPAST 处理一个数据库死锁的异常时候,其中一个建议就是使用 NOLOCK 或者 READPAS ...

  6. 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 ...

  7. Cmder - 在右键菜单添加"Cmder Here"

    使用命令行或终端工具的时候都有一个让我们觉得麻烦的问题,就是需要cd很多目录达到目标位置.在可视化操作系统下面我们一般都是已经处在目标目录了,这时需要执行某些命令如: python test.py 现 ...

  8. lunix 集群,负载均衡,location

       nginx location语法: location支持的语法优先级: 复制代码location匹配顺序 #  www.s14hanju.com/1.location = / {  我是代码1} ...

  9. centos7 图形化界面

    当你安装centos服务器版本的时候,系统默认是不会安装 CentOS 的图形界面程序的,比如:gnome或者kde, 那么如果你想在图形界面下工作的话,可以手动来安装CentOS Gnome GUI ...

  10. if 判断语句

    if 条件:if语句块当条件成立的时候会执行if语句块, 如果条件不成立. 不执行语句块的内容 例: money = int(input("请输入你兜里的钱:")) # 300 i ...