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

注:该图片来自于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. elasticsearch 外网访问9200端口访问

    可以访问127.0.0.1:9200,但不能访问 公网IP:9200 后面ip就是127.0.0.1的局域网ip,如何解决? 修改配置文件 config/elasticsearch.yml netwo ...

  2. Linux下用node-inspector实现NodeJS远程调试开发

    1.首先安装 node-inspector npm install -g node-inspector -g表示全局安装,如果像我一样安装失败,再试几次,npm偶尔就会这样抽风... 这一步是关键的, ...

  3. Mysql的资源(安装版+解压版)

    Mysql的安装(安装版+解压版)! 首先有几个比较好的参考,mysql官网,菜鸟教程: (1)安装版 安装版资源,SQLyog-8.32(sn)(可视化工具), 安装过程基本和安装其它程序没有什么大 ...

  4. IIS Express内存溢出错误

    IIS Express只是vs自带的一个简单版开发调试用的web服务器,所以本身貌似并不能容纳太多内存,有时候内存占用超过一定程度就会出错. 这时候可以试着发布到真正的IIS上查看一下,可能就不会出错 ...

  5. JQ替换标签与内容

    JQ: $('#Status').replaceWith(function () { return $("<select ><option value='0'>未认证 ...

  6. 安装hyperledger fabric V1.0.1

      安装文档位置: https://github.com/hyperledger/fabric   fabric代码托管地址 https://hyperledger-fabric.readthedoc ...

  7. spring controller 获取context

    想要获取context需要先熟悉spring是怎么在web容器中启动的,spring启动过程其实就是其IOC容器的启动过程,对于web程序,IOC容器启动过程即是建立上下文的过程 spring启动过程 ...

  8. C#面向对象基础2

    一.属性 作用:保护字段,对字段的赋值取值进行限定               意思是在初始化对象的时候防止出现不是事实的违规操作  如将性别赋值为‘中’ 本质:两个方法 get方法和set方法. p ...

  9. 《算法》第二章部分程序 part 1

    ▶ 书中第二章部分程序,加上自己补充的代码,包括插入排序,选择排序,Shell 排序 ● 插入排序 package package01; import java.util.Comparator; im ...

  10. 精通Web Analytics 2.0 (12) 第十章:针对潜在的网站分析陷阱的最佳解决方案

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第十章:针对潜在的网站分析陷阱的最佳解决方案 是时候去处理网站分析中最棘手的一些问题了,然后获得属于你的黑带,这是成为分析忍者的 ...