Hibernate Validator

Hibernate Validator

JSR 303 的参考实现

使用指南

由  Hardy Ferentschik和Gunnar Morling
and thanks to  Shaozhuang Liu

4.2.0.Finalphp

June 20, 2011java


序言
1. 开始入门
1.1. 第一个Maven项目
1.2. 添加约束
1.3. 校验约束
1.4. 更进一步
2. Validation step by step
2.1. 定义约束
2.1.1. 字段级(field level) 约束
2.1.2. 属性级别约束
2.1.3. 类级别约束
2.1.4. 约束继承
2.1.5. 对象图
2.2. 校验约束
2.2.1. 获取一个Validator的实例
2.2.2. Validator中的方法
2.2.3. ConstraintViolation 中的方法
2.2.4. 验证失败提示信息解析
2.3. 校验组
2.3.1. 校验组序列
2.3.2. 对一个类重定义其默认校验组
2.4. 内置的约束条件
2.4.1. Bean Validation constraints
2.4.2. Additional constraints
3. 建立本身的约束规则
3.1. 建立一个简单的约束条件
3.1.1. 约束标注
3.1.2. 约束校验器
3.1.3. 校验错误信息
3.1.4. 应用约束条件
3.2. 约束条件组合
4. XML configuration
4.1. validation.xml
4.2. 映射约束
5. Bootstrapping
5.1. Configuration 和 ValidatorFactory
5.2. ValidationProviderResolver
5.3. MessageInterpolator
5.3.1. ResourceBundleLocator
5.4. TraversableResolver
5.5. ConstraintValidatorFactory
6. Metadata API
6.1. BeanDescriptor
6.2. PropertyDescriptor
6.3. ElementDescriptor
6.4. ConstraintDescriptor
7. 与其余框架集成
7.1. OSGi
7.2. 与数据库集成校验
7.3. ORM集成
7.3.1. 基于Hibernate事件模型的校验
7.3.2. JPA
7.4. 展现层校验
8. Hibernate Validator Specifics
8.1. Public API
8.2. Fail fast mode
8.3. Method validation
8.3.1. Defining method-level constraints
8.3.2. Evaluating method-level constraints
8.3.3. Retrieving method-level constraint meta data
8.4. Programmatic constraint definition
8.5. Boolean composition for constraint composition
9. Annotation Processor
9.1. 前提条件
9.2. 特性
9.3. 配置项
9.4. 使用标注处理器
9.4.1. 命令行编译
9.4.2. IDE集成
9.5. 已知问题
10. 进一步阅读

序言

数据校验是任何一个应用程序都会用到的功能,不管是显示层仍是持久层. 一般,相同的校验逻辑会分散在各个层中, 这样,不只浪费了时间还会致使错误的发生(译注: 重复代码). 为了不重复, 开发人员常常会把这些校验逻辑直接写在领域模型里面, 可是这样又把领域模型代码和校验代码混杂在了一块儿, 而这些校验逻辑更应该是描述领域模型的元数据.node

JSR 303 - Bean Validation - 为实体验证定义了元数据模型和API. 默认的元数据模型是经过Annotations来描述的,可是也可使用XML来重载或者扩展. Bean Validation API 并不局限于应用程序的某一层或者哪一种编程模型, 例如,如图所示, Bean Validation 能够被用在任何一层, 或者是像相似Swing的富客户端程序中.git

Hibernate Validator is the reference implementation of this JSR. The implementation itself as well as the Bean Validation API and TCK are all provided and distributed under the Apache Software License 2.0.github

第 1 章 开始入门

本章将会告诉你如何使用Hibernate Validator, 在开始以前,你须要准备好下面的环境:正则表达式

  • A JDK >= 5数据库

  • Apache Mavenexpress

  • 网络链接 ( Maven须要经过互联网下载所需的类库)apache

  • A properly configured remote repository. Add the following to your settings.xml:

    例 1.1. Configuring the JBoss Maven repository

    <repositories>
        <repository>
            <id>jboss-public-repository-group</id>
            <url>https://repository.jboss.org/nexus/content/groups/public-jboss</url>
            <releases>
              <enabled>true</enabled>
            </releases>
            <snapshots>
              <enabled>true</enabled>
            </snapshots>
         </repository>
    </repositories>        


    More information about settings.xml can be found in the Maven Local Settings Model.

注意

Hibernate Validator uses JAXB for XML parsing. JAXB is part of the Java Class Library since Java 6 which means that if you run Hibernate Validator with Java 5 you will have to add additional JAXB dependencies. Using Maven you have to add the following dependencies:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.2</version>
</dependency>
<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.1.12</version>
</dependency>

if you are using the SourceForge package you find the necessary libraries in the lib/jdk5 directory. In case you are not using the XML configuration you can also disable it explicitly by calling Configuration.ignoreXmlConfiguration() during ValidationFactory creation. In this case the JAXB dependencies are not needed.

1.1. 第一个Maven项目

使用Maven archetype插件来建立一个新的Maven 项目

例 1.2. 使用Maven archetype 插件来建立一个简单的基于Hibernate Validator的项目

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

 

Maven 将会把你的项目建立在hv-quickstart目录中. 进入这个目录而且执行:

mvn test

这样, Maven会编译示例代码而且运行单元测试, 接下来,让咱们看看生成的代码.

注意

From version 4.2.0.Beta2, the maven command mvn archetype:create will be no longer supported and will fail. You should use the command described in the above listing. If you want more details, look at Maven Archetype plugin page.

1.2. 添加约束

在你喜欢的IDE中打开这个项目中的Car类:

例 1.3. 带约束性标注(annotated with constraints)的Car 类

package com.mycompany; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; public class Car {     @NotNull     private String manufacturer;     @NotNull     @Size(min = 2, max = 14)     private String licensePlate;     @Min(2)     private int seatCount;          public Car(String manufacturer, String licencePlate, int seatCount) {         this.manufacturer = manufacturer;         this.licensePlate = licencePlate;         this.seatCount = seatCount;     }     //getters and setters ... }

@NotNull@Size and @Min就是上面所属的约束性标注( constraint annotations), 咱们就是使用它们来声明约束, 例如在Car的字段中咱们能够看到:

  • manufacturer永远不能为null

  • licensePlate永远不能为null,而且它的值字符串的长度要在2到14之间

  • seatCount的值要不能小于2

1.3. 校验约束

咱们须要使用Validator来对上面的那些约束进行校验. 让咱们来看看CarTest这个类:

例 1.4. 在CarTest中使用校验

package com.mycompany; import static org.junit.Assert.*; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import org.junit.BeforeClass; import org.junit.Test; public class CarTest {     private static Validator validator;     @BeforeClass     public static void setUp() {         ValidatorFactory factory = Validation.buildDefaultValidatorFactory();         validator = factory.getValidator();     }     @Test     public void manufacturerIsNull() {         Car car = new Car(null, "DD-AB-123", 4);         Set<ConstraintViolation<Car>> constraintViolations =             validator.validate(car);         assertEquals(1, constraintViolations.size());         assertEquals("may not be null", constraintViolations.iterator().next().getMessage());     }     @Test     public void licensePlateTooShort() {         Car car = new Car("Morris", "D", 4);         Set<ConstraintViolation<Car>> constraintViolations =              validator.validate(car);         assertEquals(1, constraintViolations.size());         assertEquals("size must be between 2 and 14", constraintViolations.iterator().next().getMessage());     }          @Test     public void seatCountTooLow() {         Car car = new Car("Morris", "DD-AB-123", 1);         Set<ConstraintViolation<Car>> constraintViolations =             validator.validate(car);         assertEquals(1, constraintViolations.size());         assertEquals("must be greater than or equal to 2", constraintViolations.iterator().next().getMessage());     }     @Test     public void carIsValid() {         Car car = new Car("Morris", "DD-AB-123", 2);         Set<ConstraintViolation<Car>> constraintViolations =             validator.validate(car);         assertEquals(0, constraintViolations.size());     } }

setUp()方法中,咱们经过ValidatorFactory获得了一个Validator的实例. Validator是线程安全的,而且能够重复使用, 因此咱们把它保存成一个类变量. 如今咱们能够在test方法中使用这个validator的实例来校验不一样的car实例了.

validate()方法会返回一个set的ConstraintViolation的实例的集合, 咱们能够经过遍历它来查看有哪些验证错误. 前面三个测试用例显示了一些预期的校验约束:

  • manufacturerIsNull()中能够看到manufacturer违反了@NotNull约束

  • licensePlateTooShort()中的licensePlate违反了@Size约束

  • seatCountTooLow()中则致使seatCount违反了@Min约束

若是一个对象没有校验出问题的话,那么validate() 会返回一个空的set对象.

注意,咱们只使用了Bean Validation API中的package javax.validation中的类, 并无直接调用参考实现中的任何类,因此, 没有任何问题若是切换到其余的实现.

1.4. 更进一步

That concludes our 5 minute tour through the world of Hibernate Validator. Continue exploring the code examples or look at further examples referenced in 第 10 章 进一步阅读. To deepen your understanding of Hibernate Validator just continue reading 第 2 章 Validation step by step. In case your application has specific validation requirements have a look at 第 3 章 建立本身的约束规则.

第 2 章 Validation step by step

在本章中,咱们会详细的介绍如何使用Hibernate Validator 来对一个给定的实体模型进行验证. 还会介绍Bean Validation规范提供了哪些默认的约束条件和Hibernate Validator提供了哪些额外的. 让咱们先从如何给一个实体添加约束开始.

2.1. 定义约束

Bean Validation 的约束是经过Java 注解(annotations)来标注的. 在本节中,咱们会介绍如何使用这些注解(annotations)来标注一个实体模型. 而且,咱们会区分三种不通的注解(annotations) 类型.

注意

不是全部的约束都可以被用在全部的类结构上. 事实上, 没有任何定义在Bean Validation规范中的约束能够被用在class上. 约束定义中的java.lang.annotation.Target属性定义了这个约束可以被使用在哪一个层次结构上. 详细信息请参考第 3 章 建立本身的约束规则.

2.1.1. 字段级(field level) 约束

约束条件可以被标注在类的字段上面, 请参考示例例 2.1 “字段级(field level) 约束”

例 2.1. 字段级(field level) 约束

package com.mycompany; import javax.validation.constraints.NotNull; public class Car {     @NotNull     private String manufacturer;     @AssertTrue     private boolean isRegistered;     public Car(String manufacturer, boolean isRegistered) {         super();         this.manufacturer = manufacturer;         this.isRegistered = isRegistered;     } }

当约束被定义在字段上的时候, 这个字段的值是经过字段访问策略来获取并验证的. 也就是说Bean Validation的实现者会直接访问这个实例变量而不会调用属性的访问器(getter) 即便这个方法存在.

注意

这个字段的访问级别( private, protected 或者 public) 对此没有影响.

注意

静态字段或者属性是不会被校验的.

2.1.2. 属性级别约束

若是你的模型遵循JavaBeans规范的话, 你还能够把约束标注在属性上. 例 2.2 “属性级约束”例 2.1 “字段级(field level) 约束”的惟一不一样就是它的约束是定义在属性级别上的.

注意

若是要定义约束在属性级别上的话,那么只能定义在访问器(getter)上面,不能定义在修改器(setter)上.

例 2.2. 属性级约束

package com.mycompany; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotNull; public class Car {     private String manufacturer;     private boolean isRegistered;            public Car(String manufacturer, boolean isRegistered) {         super();         this.manufacturer = manufacturer;         this.isRegistered = isRegistered;     }     @NotNull     public String getManufacturer() {         return manufacturer;     }     public void setManufacturer(String manufacturer) {         this.manufacturer = manufacturer;     }     @AssertTrue     public boolean isRegistered() {         return isRegistered;     }     public void setRegistered(boolean isRegistered) {         this.isRegistered = isRegistered;     } }

When using property level constraints property access strategy is used to access the value to be validated. This means the bean validation provider accesses the state via the property accessor method. One advantage of annotating properties instead of fields is that the constraints become part of the constrained type's API that way and users are aware of the existing constraints without having to examine the type's implementation.

提示

It is recommended to stick either to field or property annotations within one class. It is not recommended to annotate a field and the accompanying getter method as this would cause the field to be validated twice.

2.1.3. 类级别约束

最后, 一个约束也可以被放在类级别上. 当一个约束被标注在一个类上的时候,这个类的实例对象被传递给ConstraintValidator. 当须要同时校验多个属性来验证一个对象或者一个属性在验证的时候须要另外的属性的信息的时候, 类级别的约束会颇有用. 在例 2.3 “类级别约束”中, 咱们给类Car添加了一个passengers的属性. 而且咱们还标注了一个PassengerCount约束在类级别上. 稍后会看到咱们是如何建立这个自定义的约束的(第 3 章 建立本身的约束规则). 如今,咱们能够知道,PassengerCount会保证这个车里乘客的数量不会超过它的座位数.

例 2.3. 类级别约束

package com.mycompany; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @PassengerCount public class Car {     @NotNull     private String manufacturer;     @NotNull     @Size(min = 2, max = 14)     private String licensePlate;     @Min(2)     private int seatCount;          private List<Person> passengers;          public Car(String manufacturer, String licencePlate, int seatCount) {         this.manufacturer = manufacturer;         this.licensePlate = licencePlate;         this.seatCount = seatCount;     }     //getters and setters ... }

2.1.4. 约束继承

若是要验证的对象继承于某个父类或者实现了某个接口,那么定义在父类或者接口中的约束会在验证这个对象的时候被自动加载,如同这些约束定义在这个对象所在的类中同样. 让咱们来看看下面的示例:

例 2.4. 约束继承

package com.mycompany; import javax.validation.constraints.NotNull; public class RentalCar extends Car {     private String rentalStation;          public RentalCar(String manufacturer, String rentalStation) {         super(manufacturer);         this.rentalStation = rentalStation;     }          @NotNull     public String getRentalStation() {         return rentalStation;     }     public void setRentalStation(String rentalStation) {         this.rentalStation = rentalStation;     } }

咱们有了一个新的RentalCar类继承自前面咱们已经见到的Car, 这个子类中增长了一个rentalStation属性. 若是校验一个RentalCar的实例对象, 那么不只会验证属性rentalStation上的 @NotNull约束是否合法,还会校验父类中的manufacturer属性.

若是类Car是一个接口类型的话也是同样的效果.

若是类RentalCar 重写了父类CargetManufacturer()方法, 那么定义在父类的这个方法上的约束和子类这个方法上定义的约束都会被校验.

2.1.5. 对象图

Bean Validation API不只可以用来校验单个的实例对象,还可以用来校验完整的对象图.要使用这个功能,只须要在一个有关联关系的字段或者属性上标注@Valid. 这样,若是一个对象被校验,那么它的全部的标注了@Valid的关联对象都会被校验. 请看例 2.6 “Adding a driver to the car”.

例 2.5. Class Person

package com.mycompany; import javax.validation.constraints.NotNull; public class Person {     @NotNull     private String name;          public Person(String name) {         super();         this.name = name;     }     public String getName() {         return name;     }     public void setName(String name) {         this.name = name;     } }

例 2.6. Adding a driver to the car

package com.mycompany; import javax.validation.Valid; import javax.validation.constraints.NotNull; public class Car {     @NotNull     @Valid     private Person driver;          public Car(Person driver) {         this.driver = driver;     }     //getters and setters ... }

若是校验Car的实例对象的话,由于它的driver属性标注了@Valid, 那么关联的Person也会被校验. 因此,若是对象Personname属性若是是null的话,那么校验会失败.

关联校验也适用于集合类型的字段, 也就是说,任何下列的类型:

  • 数组

  • 实现了java.lang.Iterable接口( 例如CollectionList 和 Set)

  • 实现了java.util.Map接口

若是标注了@Valid, 那么当主对象被校验的时候,这些集合对象中的元素都会被校验.

例 2.7. Car with a list of passengers

package com.mycompany; import java.util.ArrayList; import java.util.List; import javax.validation.Valid; import javax.validation.constraints.NotNull; public class Car {     @NotNull     @Valid     private List<Person> passengers = new ArrayList<Person>();     public Car(List<Person> passengers) {         this.passengers = passengers;     }     //getters and setters ... }

当校验一个Car的实例的时候,若是passengers list中包含的任何一个Person对象没有名字的话,都会致使校验失败(a ConstraintValidation will be created).

注意

对象图校验的时候是会被忽略null值的.

2.2. 校验约束

Validator 是Bean Validation中最主要的接口, 咱们会在第 5.1 节 “Configuration 和 ValidatorFactory”中详细介绍如何获取一个Validator的实例, 如今先让咱们来看看如何使用Validator接口中的各个方法.

2.2.1. 获取一个Validator的实例

对一个实体对象验证以前首先须要有个Validator对象, 而这个对象是须要经过Validation 类和 ValidatorFactory来建立的. 最简单的方法是调用Validation.buildDefaultValidatorFactory() 这个静态方法.

例 2.8. Validation.buildDefaultValidatorFactory()

ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator();

第 5 章 Bootstrapping介绍了其余的获取Validator实例的方法. 如今咱们的目标是学习如何使用Validator 来校验实体对象.

2.2.2. Validator中的方法

Validator中有三个方法可以被用来校验整个实体对象或者实体对象中的属性.

这三个方法都会返回一个Set<ConstraintViolation>对象, 若是整个验证过程没有发现问题的话,那么这个set是空的, 不然, 每一个违反约束的地方都会被包装成一个ConstraintViolation的实例而后添加到set当中.

全部的校验方法都接收零个或多个用来定义这次校验是基于哪一个校验组的参数. 若是没有给出这个参数的话, 那么这次校验将会基于默认的校验组 (javax.validation.groups.Default). 第 2.3 节 “校验组”

2.2.2.1. validate

使用validate()方法对一个给定的实体对象中定义的全部约束条件进行校验 (例 2.9 “Validator.validate() 使用方法” ).

例 2.9. Validator.validate() 使用方法

ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); Car car = new Car(null); Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car); assertEquals(1, constraintViolations.size()); assertEquals("may not be null", constraintViolations.iterator().next().getMessage());

2.2.2.2. validateProperty

经过validateProperty()能够对一个给定实体对象的单个属性进行校验. 其中属性名称须要符合JavaBean规范中定义的属性名称.

例 2.10. Validator.validateProperty()使用方法

Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); Car car = new Car(null); Set<ConstraintViolation<Car>> constraintViolations = validator.validateProperty(car, "manufacturer"); assertEquals(1, constraintViolations.size()); assertEquals("may not be null", constraintViolations.iterator().next().getMessage());

例如, Validator.validateProperty能够被用在把Bean Validation集成进JSF 2中的时候使用 (请参考 第 7.4 节 “展现层校验”).

2.2.2.3. validateValue

经过validateValue() 方法,你可以校验若是把一个特定的值赋给一个类的某一个属性的话,是否会违反此类中定义的约束条件.

例 2.11. Validator.validateValue() 使用方法

Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); Set<ConstraintViolation<Car>> constraintViolations = validator.validateValue(Car.class, "manufacturer", null); assertEquals(1, constraintViolations.size()); assertEquals("may not be null", constraintViolations.iterator().next().getMessage());

注意

validateProperty() 和 validateValue() 会忽略被验证属性上定义的@Valid.

2.2.3. ConstraintViolation 中的方法

如今是时候看看究竟ConstraintViolation是什么了. ConstraintViolation中包含了不少方法可以帮你快速定位到底是什么致使了校验失败.表 2.1 “ConstraintViolation 中的方法” 列出了这些方法:

表 2.1. ConstraintViolation 中的方法

方法名 做用 示例 (请参考例 2.9 “Validator.validate() 使用方法”)
getMessage() 获取(通过翻译的)校验错误信息 may not be null
getMessageTemplate() 获取错误信息模版 {javax.validation.constraints.NotNull.message}
getRootBean() 获取被校验的根实体对象 car
getRootBeanClass() 获取被校验的根实体类. Car.class
getLeafBean() 若是约束是添加在一个bean(实体对象)上的,那么则返回这个bean的实例, 若是是约束是定义在一个属性上的, 则返回这个属性所属的bean的实例对象. car
getPropertyPath() 从被验证的根对象到被验证的属性的路径.  
getInvalidValue() 却是校验失败的值. passengers
getConstraintDescriptor() 致使校验失败的约束定义.  

2.2.4. 验证失败提示信息解析

第 3 章 建立本身的约束规则中,咱们会看到,每一个约束定义中都包含有一个用于提示验证结果的消息模版, 而且在声明一个约束条件的时候,你能够经过这个约束中的message属性来重写默认的消息模版, 能够参考例 2.13 “Driver”. 若是在校验的时候,这个约束条件没有经过,那么你配置的MessageInterpolator会被用来当成解析器来解析这个约束中定义的消息模版, 从而获得最终的验证失败提示信息. 这个解析器会尝试解析模版中的占位符( 大括号括起来的字符串 ). 其中, Hibernate Validator中默认的解析器 (MessageInterpolator) 会先在类路径下找名称为ValidationMessages.propertiesResourceBundle, 而后将占位符和这个文件中定义的resource进行匹配,若是匹配不成功的话,那么它会继续匹配Hibernate Validator自带的位于/org/hibernate/validator/ValidationMessages.propertiesResourceBundle, 依次类推,递归的匹配全部的占位符.

由于大括号{ 在这里是特殊字符,因此,你能够经过使用反斜线来对其进行转义, 例如:

  • \{ 被转义成 {

  • \} 被转义成 }

  • \\ 被转义成 \

若是默认的消息解析器不可以知足你的需求,那么你也能够在建立ValidatorFactory的时候, 将其替换为一个你自定义的MessageInterpolator, 具体请参考 第 5 章 Bootstrapping.

2.3. 校验组

校验组可以让你在验证的时候选择应用哪些约束条件. 这样在某些状况下( 例如向导 ) 就能够对每一步进行校验的时候, 选取对应这步的那些约束条件进行验证了. 校验组是经过可变参数传递给validatevalidateProperty 和 validateValue的. 让咱们来看个例子, 这个实例扩展了上面的Car类,又为其添加了一个新的Driver. 首先, 类Person (例 2.12 “Person”) 的name属性上定义了一个@NotNull 的约束条件. 由于没有明确指定这个约束条件属于哪一个组,因此它被归类到默认组 (javax.validation.groups.Default).

注意

若是某个约束条件属于多个组,那么各个组在校验时候的顺序是不可预知的. 若是一个约束条件没有被指明属于哪一个组,那么它就会被归类到默认组(javax.validation.groups.Default).

例 2.12. Person

public class Person {     @NotNull     private String name;     public Person(String name) {         this.name = name;     }     // getters and setters ... }

接下来, 咱们让类Driver (例 2.13 “Driver”) 继承自类Person. 而后添加两个属性,分别是age 和 hasDrivingLicense. 对于一个司机来讲, 要开车的话, 必须知足两个条件, 年满18周岁 (@Min(18)) 和你的有驾照(@AssertTrue). 这两个约束条件分别定义在那两个属性上, 而且把他们都归于DriverChecks组. 经过例 2.14 “Group interfaces”, 你能够看到, "DriverChecks组" 就是一个简单的标记接口. 使用接口( 而不是字符串) 能够作到类型安全,而且接口比字符串更加对重构友好, 另外, 接口还意味着一个组能够继承别的组.

例 2.13. Driver

public class Driver extends Person {     @Min(value = 18, message = "You have to be 18 to drive a car", groups = DriverChecks.class)     public int age;     @AssertTrue(message = "You first have to pass the driving test", groups = DriverChecks.class)     public boolean hasDrivingLicense;     public Driver(String name) {         super( name );     }     public void passedDrivingTest(boolean b) {         hasDrivingLicense = b;     }     public int getAge() {         return age;     }     public void setAge(int age) {         this.age = age;     } }

例 2.14. Group interfaces

public interface DriverChecks { } public interface CarChecks { }

最后, 咱们给Car class (例 2.15 “Car”) 添加一个passedVehicleInspection的属性,来表示这个车是否经过了上路检查.

例 2.15. Car

public class Car {     @NotNull     private String manufacturer;     @NotNull     @Size(min = 2, max = 14)     private String licensePlate;     @Min(2)     private int seatCount;     @AssertTrue(message = "The car has to pass the vehicle inspection first", groups = CarChecks.class)     private boolean passedVehicleInspection;     @Valid     private Driver driver;     public Car(String manufacturer, String licencePlate, int seatCount) {         this.manufacturer = manufacturer;         this.licensePlate = licencePlate;         this.seatCount = seatCount;     } }

如今, 在咱们的例子中有三个不一样的校验组, Person.name, Car.manufacturer, Car.licensePlate 和 Car.seatCount都属于默认(Default) 组, Driver.age 和 Driver.hasDrivingLicense 从属于 DriverChecks组, 而Car.passedVehicleInspection 在CarChecks组中. 例 2.16 “Drive away”演示了如何让Validator.validate验证不一样的组来获得不一样的校验结果.

例 2.16. Drive away

public class GroupTest {     private static Validator validator;     @BeforeClass     public static void setUp() {         ValidatorFactory factory = Validation.buildDefaultValidatorFactory();         validator = factory.getValidator();     }     @Test     public void driveAway() {         // create a car and check that everything is ok with it.         Car car = new Car( "Morris", "DD-AB-123", 2 );         Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );         assertEquals( 0, constraintViolations.size() );         // but has it passed the vehicle inspection?         constraintViolations = validator.validate( car, CarChecks.class );         assertEquals( 1, constraintViolations.size() );         assertEquals("The car has to pass the vehicle inspection first", constraintViolations.iterator().next().getMessage());         // let's go to the vehicle inspection         car.setPassedVehicleInspection( true );         assertEquals( 0, validator.validate( car ).size() );         // now let's add a driver. He is 18, but has not passed the driving test yet         Driver john = new Driver( "John Doe" );         john.setAge( 18 );         car.setDriver( john );         constraintViolations = validator.validate( car, DriverChecks.class );         assertEquals( 1, constraintViolations.size() );         assertEquals( "You first have to pass the driving test", constraintViolations.iterator().next().getMessage() );         // ok, John passes the test         john.passedDrivingTest( true );         assertEquals( 0, validator.validate( car, DriverChecks.class ).size() );         // just checking that everything is in order now         assertEquals( 0, validator.validate( car, Default.class, CarChecks.class, DriverChecks.class ).size() );     } }

首先咱们建立一辆汽车而后在没有明确指定使用哪一个校验组的状况下校验它, 能够看到即便passedVehicleInspection的默认值是false也不会校验出错误来. 由于定义在这个属性上的约束条件并不属于默认的校验组, 接下来,咱们来校验CarChecks这个组, 这样就会发现car违反了约束条件, 必须让这个车先经过检测. 接下来,咱们给这个车增长一个司机, 而后在基于DriverChecks来校验, 会发现由于这个司机由于尚未经过驾照考试, 因此又一次获得了校验错误, 若是咱们设置passedDrivingTest属性为true以后, DriverChecks组的校验就经过了.

最后, 让咱们再来校验全部的组中定义的约束条件,能够看到全部的约束条件都经过了验证.

2.3.1. 校验组序列

By default, constraints are evaluated in no particular order and this regardless of which groups they belong to. In some situations, however, it is useful to control the order of the constraint evaluation. In our example from 第 2.3 节 “校验组” we could for example require that first all default car constraints are passing before we check the road worthiness of the car. Finally before we drive away we check the actual driver constraints. In order to implement such an order one would define a new interface and annotate it with @GroupSequence defining the order in which the groups have to be validated.

注意

若是这个校验组序列中有一个约束条件没有经过验证的话, 那么此约束条件后面的都不会再继续被校验了.

例 2.17. 标注了@GroupSequence的接口

@GroupSequence({Default.class, CarChecks.class, DriverChecks.class}) public interface OrderedChecks { }

警告

一个校验组序列中包含的校验组和这个校验组序列不能形成直接或者间接的循环引用. 包括校验组继承. 若是形成了循环引用的话, 会致使GroupDefinitionException异常.

例 2.18 “校验组序列的用法”展现了校验组序列的用法.

例 2.18. 校验组序列的用法

@Test public void testOrderedChecks() {     Car car = new Car( "Morris", "DD-AB-123", 2 );     car.setPassedVehicleInspection( true );     Driver john = new Driver( "John Doe" );     john.setAge( 18 );     john.passedDrivingTest( true );     car.setDriver( john );     assertEquals( 0, validator.validate( car, OrderedChecks.class ).size() ); }

2.3.2. 对一个类重定义其默认校验组

2.3.2.1. @GroupSequence

The @GroupSequence annotation also fulfills a second purpose. It allows you to redefine what the Default group means for a given class. To redefine Default for a given class, add a @GroupSequence annotation to the class. The defined groups in the annotation express the sequence of groups that substitute Default for this class. 例 2.19 “RentalCar with @GroupSequence” introduces a new class RentalCar with a redefined default group. With this definition the check for all three groups can be rewritten as seen in 例 2.20 “testOrderedChecksWithRedefinedDefault”.

例 2.19. RentalCar with @GroupSequence

@GroupSequence({ RentalCar.class, CarChecks.class, DriverChecks.class }) public class RentalCar extends Car {     private boolean rented;         public RentalCar(String manufacturer, String licencePlate, int seatCount) {         super( manufacturer, licencePlate, seatCount );     }    public boolean isRented() {         return rented;     }     public void setRented(booelan rented) {         this.rented = rented;     } }

例 2.20. testOrderedChecksWithRedefinedDefault

@Test public void testOrderedChecksWithRedefinedDefault() {     RentalCar rentalCar = new RentalCar( "Morris", "DD-AB-123", 2 );     rentalCar.setPassedVehicleInspection( true );     Driver john = new Driver( "John Doe" );     john.setAge( 18 );     john.passedDrivingTest( true );     rentalCar.setDriver( john );     assertEquals( 0, validator.validate( rentalCar, Default.class ).size() ); }

注意

由于不能在校验组和校验组序列中有循环依赖关系, 因此, 若是你想重定义一个类的默认组, 而且还想把Default组加入到这个重定义的序列当中的话, 则不能简单的加入Default, 而是须要把被重定义的类加入到其中.

2.3.2.2. @GroupSequenceProvider

The @javax.validation.GroupSequence annotation is a standardized Bean Validation annotation. As seen in the previous section it allows you to statically redefine the default group sequence for a class. Hibernate Validator also offers a custom, non standardized annotation - org.hibernate.validator.group.GroupSequenceProvider - which allows for dynamic redefinition of the default group sequence. Using the rental car scenario again, one could dynamically add the driver checks depending on whether the car is rented or not. 例 2.21 “RentalCar with @GroupSequenceProvider” and 例  “DefaultGroupSequenceProvider implementation” show how this use-case would be implemented.

例 2.21. RentalCar with @GroupSequenceProvider

@GroupSequenceProvider(RentalCarGroupSequenceProvider.class) public class RentalCar extends Car {     private boolean rented;         public RentalCar(String manufacturer, String licencePlate, int seatCount) {         super( manufacturer, licencePlate, seatCount );     }    public boolean isRented() {         return rented;     }     public void setRented(boolean rented) {         this.rented = rented;     } }

例 . DefaultGroupSequenceProvider implementation

public class RentalCarGroupSequenceProvider implements DefaultGroupSequenceProvider<RentalCar> {     public List<Class<?>> getValidationGroups(RentalCar car) {         List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>();         defaultGroupSequence.add( RentalCar.class, CarChecks.class );         if ( car != null && car.isRented() ) {             defaultGroupSequence.add( DriverChecks.class );         }         return defaultGroupSequence;     } }

2.4. 内置的约束条件

Hibernate Validator comprises a basic set of commonly used constraints. These are foremost the constraints defined by the Bean Validation specification (see 表 2.2 “Bean Validation constraints”). Additionally, Hibernate Validator provides useful custom constraints (see 表 2.3 “Custom constraints provided by Hibernate Validator”).

2.4.1. Bean Validation constraints

表 2.2 “Bean Validation constraints” shows purpose and supported data types of all constraints specified in the Bean Validation API. All these constraints apply to the field/property level, there are no class-level constraints defined in the Bean Validation specification. If you are using the Hibernate object-relational mapper, some of the constraints are taken into account when creating the DDL for your model (see column "Hibernate metadata impact").

注意

Hibernate Validator allows some constraints to be applied to more data types than required by the Bean Validation specification (e.g. @Max can be applied to Strings). Relying on this feature can impact portability of your application between Bean Validation providers.

表 2.2. Bean Validation constraints

Annotation Supported data types 做用 Hibernate metadata impact
@AssertFalse Booleanboolean Checks that the annotated element is false. 没有
@AssertTrue Booleanboolean Checks that the annotated element is true. 没有
@DecimalMax BigDecimalBigIntegerStringbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number. 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个经过BigDecimal定义的最大值的字符串表示. 没有
@DecimalMin BigDecimalBigIntegerStringbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number. 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个经过BigDecimal定义的最小值的字符串表示. 没有
@Digits(integer=, fraction=) BigDecimalBigIntegerStringbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number. Checks whether the annoted value is a number having up to integer digits and fraction fractional digits. 对应的数据库表字段会被设置精度(precision)和准度(scale).
@Future java.util.Datejava.util.Calendar; Additionally supported by HV, if the Joda Time date/time API is on the class path: any implementations of ReadablePartial and ReadableInstant. 检查给定的日期是否比如今晚. 没有
@Max BigDecimalBigIntegerbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: String (the numeric value represented by a String is evaluated), any sub-type of Number. 检查该值是否小于或等于约束条件中指定的最大值. 会给对应的数据库表字段添加一个check的约束条件.
@Min BigDecimalBigIntegerbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: String (the numeric value represented by a String is evaluated), any sub-type of Number. 检查该值是否大于或等于约束条件中规定的最小值. 会给对应的数据库表字段添加一个check的约束条件.
@NotNull Any type Checks that the annotated value is not null. 对应的表字段不容许为null.
@Null Any type Checks that the annotated value is null. 没有
@Past java.util.Datejava.util.Calendar; Additionally supported by HV, if the Joda Time date/time API is on the class path: any implementations of ReadablePartial and ReadableInstant. 检查标注对象中的值表示的日期比当前早. 没有
@Pattern(regex=, flag=) String 检查该字符串是否可以在match指定的状况下被regex定义的正则表达式匹配. 没有
@Size(min=, max=) StringCollectionMap and arrays Checks if the annotated element's size is between min and max (inclusive). 对应的数据库表字段的长度会被设置成约束中定义的最大值.
@Valid Any non-primitive type 递归的对关联对象进行校验, 若是关联对象是个集合或者数组, 那么对其中的元素进行递归校验,若是是一个map,则对其中的值部分进行校验. 没有

注意

On top of the parameters indicated in 表 2.2 “Bean Validation constraints” each constraint supports the parameters messagegroups and payload. This is a requirement of the Bean Validation specification.

2.4.2. Additional constraints

In addition to the constraints defined by the Bean Validation API Hibernate Validator provides several useful custom constraints which are listed in 表 2.3 “Custom constraints provided by Hibernate Validator”. With one exception also these constraints apply to the field/property level, only @ScriptAssert is a class-level constraint.

表 2.3. Custom constraints provided by Hibernate Validator

Annotation Supported data types 做用 Hibernate metadata impact
@CreditCardNumber String Checks that the annotated string passes the Luhn checksum test. Note, this validation aims to check for user mistakes, not credit card validity! See also Anatomy of Credit Card Numbers. 没有
@Email String Checks whether the specified string is a valid email address. 没有
@Length(min=, max=) String Validates that the annotated string is between min and max included. 对应的数据库表字段的长度会被设置成约束中定义的最大值.
@NotBlank String Checks that the annotated string is not null and the trimmed length is greater than 0. The difference to @NotEmpty is that this constraint can only be applied on strings and that trailing whitespaces are ignored. 没有
@NotEmpty StringCollectionMap and arrays Checks whether the annotated element is not null nor empty. 没有
@Range(min=, max=) BigDecimalBigIntegerStringbyteshortintlong and the respective wrappers of the primitive types Checks whether the annotated value lies between (inclusive) the specified minimum and maximum. 没有
@SafeHtml(whitelistType=, additionalTags=) CharSequence Checks whether the annotated value contains potentially malicious fragments such as <script/>. In order to use this constraint, the jsoup library must be part of the class path. With the whitelistType attribute predefined whitelist types can be chosen. You can also specify additional html tags for the whitelist with the additionalTags attribute. 没有
@ScriptAssert(lang=, script=, alias=) Any type 要使用这个约束条件,必须先要保证Java Scripting API 即JSR 223 ("Scripting for the JavaTM Platform")的实如今类路径当中. 若是使用的时Java 6的话,则不是问题, 若是是老版本的话, 那么须要把 JSR 223的实现添加进类路径. 这个约束条件中的表达式可使用任何兼容JSR 223的脚原本编写. (更多信息请参考javadoc) 没有
@URL(protocol=, host=, port=, regexp=, flags=) String Checks if the annotated string is a valid URL according to RFC2396. If any of the optional parameters protocolhost or port are specified, the corresponding URL fragments must match the specified values. The optional parameters regexp and flags allow to specify an additional regular expression (including regular expression flags) which the URL must match. 没有

In some cases neither the Bean Validation constraints nor the custom constraints provided by Hibernate Validator will fulfill your requirements completely. In this case you can literally in a minute write your own constraints. We will discuss this in 第 3 章 建立本身的约束规则.

第 3 章 建立本身的约束规则

尽管Bean Validation API定义了一大堆标准的约束条件, 可是确定仍是有这些约束不能知足咱们需求的时候, 在这种状况下, 你能够根据你的特定的校验需求来建立本身的约束条件.

3.1. 建立一个简单的约束条件

按照如下三个步骤来建立一个自定义的约束条件

  • 建立约束标注

  • 实现一个验证器

  • 定义默认的验证错误信息

3.1.1. 约束标注

让咱们来建立一个新的用来判断一个给定字符串是否全是大写或者小写字符的约束标注. 咱们将稍后把它用在第 1 章 开始入门中的类CarlicensePlate字段上来确保这个字段的内容一直都是大写字母.

首先,咱们须要一种方法来表示这两种模式( 译注: 大写或小写), 咱们可使用String常量, 可是在Java 5中, 枚举类型是个更好的选择:

例 3.1. 枚举类型CaseMode, 来表示大写或小写模式.

package com.mycompany; public enum CaseMode {     UPPER,      LOWER; }

如今咱们能够来定义真正的约束标注了. 若是你之前没有建立过标注(annotation)的话,那么这个可能看起来有点吓人, 但是其实没有那么难的 :)

例 3.2. 定义一个CheckCase的约束标注

package com.mycompany; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; @Target( { METHOD, FIELD, ANNOTATION_TYPE }) @Retention(RUNTIME) @Constraint(validatedBy = CheckCaseValidator.class) @Documented public @interface CheckCase {     String message() default "{com.mycompany.constraints.checkcase}";     Class<?>[] groups() default {};     Class<? extends Payload>[] payload() default {};          CaseMode value(); }

一个标注(annotation) 是经过@interface关键字来定义的. 这个标注中的属性是声明成相似方法的样式的. 根据Bean Validation API 规范的要求

  • message属性, 这个属性被用来定义默认得消息模版, 当这个约束条件被验证失败的时候,经过此属性来输出错误信息.

  • groups 属性, 用于指定这个约束条件属于哪(些)个校验组(请参考第 2.3 节 “校验组”). 这个的默认值必须是Class<?>类型到空到数组.

  • payload 属性, Bean Validation API 的使用者能够经过此属性来给约束条件指定严重级别. 这个属性并不被API自身所使用.

    提示

    经过payload属性来指定默认错误严重级别的示例

    public class Severity {
        public static class Info extends Payload {};
        public static class Error extends Payload {};
    }
    
    public class ContactDetails {
        @NotNull(message="Name is mandatory", payload=Severity.Error.class)
        private String name;
    
        @NotNull(message="Phone number not specified, but not mandatory", payload=Severity.Info.class)
        private String phoneNumber;
    
        // ...
    }

    这样, 在校验完一个ContactDetails 的示例以后, 你就能够经过调用ConstraintViolation.getConstraintDescriptor().getPayload()来获得以前指定到错误级别了,而且能够根据这个信息来决定接下来到行为.

除了这三个强制性要求的属性(message, groups 和 payload) 以外, 咱们还添加了一个属性用来指定所要求到字符串模式. 此属性的名称value在annotation的定义中比较特殊, 若是只有这个属性被赋值了的话, 那么, 在使用此annotation到时候能够忽略此属性名称, 即@CheckCase(CaseMode.UPPER).

另外, 咱们还给这个annotation标注了一些(所谓的) 元标注( 译注: 或"元模型信息"?, "meta annotatioins"):

  • @Target({ METHOD, FIELD, ANNOTATION_TYPE }): 表示@CheckCase 能够被用在方法, 字段或者annotation声明上.

  • @Retention(RUNTIME): 表示这个标注信息是在运行期经过反射被读取的.

  • @Constraint(validatedBy = CheckCaseValidator.class): 指明使用那个校验器(类) 去校验使用了此标注的元素.

  • @Documented: 表示在对使用了@CheckCase的类进行javadoc操做到时候, 这个标注会被添加到javadoc当中.

提示

Hibernate Validator provides support for the validation of method parameters using constraint annotations (see 第 8.3 节 “Method validation”).

In order to use a custom constraint for parameter validation the ElementType.PARAMETER must be specified within the @Target annotation. This is already the case for all constraints defined by the Bean Validation API and also the custom constraints provided by Hibernate Validator.

3.1.2. 约束校验器

Next, we need to implement a constraint validator, that's able to validate elements with a @CheckCase annotation. To do so, we implement the interface ConstraintValidator as shown below:

例 3.3. 约束条件CheckCase的验证器

package com.mycompany; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {     private CaseMode caseMode;     public void initialize(CheckCase constraintAnnotation) {         this.caseMode = constraintAnnotation.value();     }     public boolean isValid(String object, ConstraintValidatorContext constraintContext) {         if (object == null)             return true;         if (caseMode == CaseMode.UPPER)             return object.equals(object.toUpperCase());         else             return object.equals(object.toLowerCase());     } }

ConstraintValidator定义了两个泛型参数, 第一个是这个校验器所服务到标注类型(在咱们的例子中即CheckCase), 第二个这个校验器所支持到被校验元素到类型 (即String).

若是一个约束标注支持多种类型到被校验元素的话, 那么须要为每一个所支持的类型定义一个ConstraintValidator,而且注册到约束标注中.

这个验证器的实现就很日常了, initialize() 方法传进来一个所要验证的标注类型的实例, 在本例中, 咱们经过此实例来获取其value属性的值,并将其保存为CaseMode类型的成员变量供下一步使用.

isValid()是实现真正的校验逻辑的地方, 判断一个给定的String对于@CheckCase这个约束条件来讲是不是合法的, 同时这还要取决于在initialize()中得到的大小写模式. 根据Bean Validation中所推荐的作法, 咱们认为null是合法的值. 若是null对于这个元素来讲是不合法的话,那么它应该使用@NotNull来标注.

3.1.2.1. ConstraintValidatorContext

例 3.3 “约束条件CheckCase的验证器” 中的isValid使用了约束条件中定义的错误消息模板, 而后返回一个true 或者 false. 经过使用传入的ConstraintValidatorContext对象, 咱们还能够给约束条件中定义的错误信息模板来添加额外的信息或者彻底建立一个新的错误信息模板.

例 3.4. 使用ConstraintValidatorContext来自定义错误信息

package com.mycompany; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {     private CaseMode caseMode;     public void initialize(CheckCase constraintAnnotation) {         this.caseMode = constraintAnnotation.value();     }     public boolean isValid(String object, ConstraintValidatorContext constraintContext) {         if (object == null)             return true;                  boolean isValid;         if (caseMode == CaseMode.UPPER) {             isValid = object.equals(object.toUpperCase());         }         else {             isValid = object.equals(object.toLowerCase());         }                  if(!isValid) {             constraintContext.disableDefaultConstraintViolation();             constraintContext.buildConstraintViolationWithTemplate( "{com.mycompany.constraints.CheckCase.message}"  ).addConstraintViolation();         }         return result;     } }

例 3.4 “使用ConstraintValidatorContext来自定义错误信息” 演示了若是建立一个新的错误信息模板来替换掉约束条件中定义的默认的. 在本例中, 实际上经过调用ConstraintValidatorContext达到了一个使用默认消息模板的效果.

提示

在建立新的constraint violation的时候必定要记得调用addConstraintViolation, 只有这样, 这个新的constraint violation才会被真正的建立.

In case you are implementing a ConstraintValidator a class level constraint it is also possible to adjust set the property path for the created constraint violations. This is important for the case where you validate multiple properties of the class or even traverse the object graph. A custom property path creation could look like 例 3.5 “Adding new ConstraintViolation with custom property path”.

例 3.5. Adding new ConstraintViolation with custom property path

public boolean isValid(Group group, ConstraintValidatorContext constraintValidatorContext) {     boolean isValid = false;     ...     if(!isValid) {         constraintValidatorContext             .buildConstraintViolationWithTemplate( "{my.custom.template}" )             .addNode( "myProperty" ).addConstraintViolation();     }     return isValid; } 

3.1.3. 校验错误信息

最后, 咱们还须要指定若是@CheckCase这个约束条件验证的时候,没有经过的话的校验错误信息. 咱们能够添加下面的内容到咱们项目自定义的ValidationMessages.properties (参考 第 2.2.4 节 “验证失败提示信息解析”)文件中.

例 3.6. 为CheckCase约束定义一个错误信息

com.mycompany.constraints.CheckCase.message=Case mode must be {value}.

若是发现校验错误了的话, 你所使用的Bean Validation的实现会用咱们定义在@CheckCase中message属性上的值做为键到这个文件中去查找对应的错误信息.

3.1.4. 应用约束条件

如今咱们已经有了一个自定义的约束条件了, 咱们能够把它用在第 1 章 开始入门中的Car类上, 来校验此类的licensePlate属性的值是否全都是大写字母.

例 3.7. 应用CheckCase约束条件

package com.mycompany; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; public class Car {     @NotNull     private String manufacturer;     @NotNull     @Size(min = 2, max = 14)     @CheckCase(CaseMode.UPPER)     private String licensePlate;     @Min(2)     private int seatCount;          public Car(String manufacturer, String licencePlate, int seatCount) {         this.manufacturer = manufacturer;         this.licensePlate = licencePlate;         this.seatCount = seatCount;     }     //getters and setters ... }

最后,让咱们用一个简单的测试来检测@CheckCase约束已经被正确的校验了:

例 3.8. 演示CheckCase的验证过程

package com.mycompany; import static org.junit.Assert.*; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import org.junit.BeforeClass; import org.junit.Test; public class CarTest {     private static Validator validator;     @BeforeClass     public static void setUp() {         ValidatorFactory factory = Validation.buildDefaultValidatorFactory();         validator = factory.getValidator();     }     @Test     public void testLicensePlateNotUpperCase() {         Car car = new Car("Morris", "dd-ab-123", 4);         Set<ConstraintViolation<Car>> constraintViolations =             validator.validate(car);         assertEquals(1, constraintViolations.size());         assertEquals(             "Case mode must be UPPER.",              constraintViolations.iterator().next().getMessage());     }     @Test     public void carIsValid() {         Car car = new Car("Morris", "DD-AB-123", 4);         Set<ConstraintViolation<Car>> constraintViolations =             validator.validate(car);         assertEquals(0, constraintViolations.size());     } }

3.2. 约束条件组合

例 3.7 “应用CheckCase约束条件”中咱们能够看到, 类CarlicensePlate属性上定义了三个约束条件. 在某些复杂的场景中, 可能还会有更多的约束条件被定义到同一个元素上面, 这可能会让代码看起来有些复杂, 另外, 若是在另外的类里面还有一个licensePlate属性, 咱们可能还要把这些约束条件再拷贝到这个属性上, 可是这样作又违反了 DRY 原则.

这个问题能够经过使用组合约束条件来解决. 接下来让咱们来建立一个新的约束标注@ValidLicensePlate, 它组合了@NotNull@Size 和 @CheckCase:

例 3.9. 建立一个约束条件组合ValidLicensePlate

package com.mycompany; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @NotNull @Size(min = 2, max = 14) @CheckCase(CaseMode.UPPER) @Target( { METHOD, FIELD, ANNOTATION_TYPE }) @Retention(RUNTIME) @Constraint(validatedBy = {}) @Documented public @interface ValidLicensePlate {     String message() default "{com.mycompany.constraints.validlicenseplate}";     Class<?>[] groups() default {};     Class<? extends Payload>[] payload() default {}; }

咱们只须要把要组合的约束标注在这个新的类型上加以声明 (注: 这正是咱们为何把annotation types做为了@CheckCase的一个target). 由于这个组合不须要额外的校验器, 因此不须要声明validator属性.

如今, 在licensePlate属性上使用这个新定义的"约束条件" (实际上是个组合) 和以前在其上声明那三个约束条件是同样的效果了.

例 3.10. 使用ValidLicensePlate组合约束

package com.mycompany; public class Car {     @ValidLicensePlate     private String licensePlate;     //... }

The set of ConstraintViolations retrieved when validating a Car instance will contain an entry for each violated composing constraint of the @ValidLicensePlate constraint. If you rather prefer a single ConstraintViolation in case any of the composing constraints is violated, the @ReportAsSingleViolation meta constraint can be used as follows:

例 3.11. @ReportAsSingleViolation的用法

//... @ReportAsSingleViolation public @interface ValidLicensePlate {     String message() default "{com.mycompany.constraints.validlicenseplate}";     Class<?>[] groups() default {};     Class<? extends Payload>[] payload() default {}; }

第 4 章 XML configuration

4.1. validation.xml

咱们可使用validation.xml来对Hibernate Validator进行配置. ValidationFactory在初始化的时候会在类路径下寻找此文件,若是找到的话,就会应用其中定义的配置信息. 例 4.1 “validation-configuration-1.0.xsd”显示了valiation.xml的xsd模型.

例 4.1. validation-configuration-1.0.xsd

 

例 4.2 “validation.xml” 列出了validation.xml中的一些经常使用的配置项.

例 4.2. validation.xml

<validation-config xmlns="http://jboss.org/xml/ns/javax/validation/configuration"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration">     <default-provider>org.hibernate.validator.HibernateValidator</default-provider>     <message-interpolator>org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator</message-interpolator>     <traversable-resolver>org.hibernate.validator.engine.resolver.DefaultTraversableResolver</traversable-resolver>     <constraint-validator-factory>org.hibernate.validator.engine.ConstraintValidatorFactoryImpl</constraint-validator-factory>     <constraint-mapping>/constraints-car.xml</constraint-mapping>     <property name="prop1">value1</property>     <property name="prop2">value2</property> </validation-config>

警告

类路径下面只能有一个validation.xml, 若是超过一个的话,会抛出异常.

validation.xml中全部的配置信息都是可选的, 例 4.2 “validation.xml”中就是列出了Hibernate Validator中的默认值. 若是类路径当中存在有多个Bean Validation的实现的话, 那么能够经过default-provider节点指定使用那个Bean Validation的实现. message-interpolator, traversable-resolver 和 constraint-validator-factory能够用来指定自定义的javax.validation.MessageInterpolatorjavax.validation.TraversableResolverjavax.validation.ConstraintValidatorFactory. 一样的, 这些配置信息也能够经过编程的方式调用javax.validation.Configuration来实现. 另外, 你能够经过API的方式来重写xml中的配置信息, 也能够经过调用 Configuration.ignoreXmlConfiguration()来彻底的忽略掉xml的配置信息. 请参考第 5 章 Bootstrapping.

你能够增长若干个constraint-mapping节点,在每一个里面列出一个额外的xml文件用来定义约束规则, 具体请参考第 4.2 节 “映射约束”.

Last but not least, you can specify provider specific properties via the property nodes.

4.2. 映射约束

咱们也能够经过xml来定义约束条件, 只须要这个xml符合例 4.3 “validation-mapping-1.0.xsd”中所定义的规范. 须要注意的是, 你必须把xml定义的约束列在validation.xmlconstraint-mapping节点中才能获得处理.

例 4.3. validation-mapping-1.0.xsd


例 4.4 “constraints-car.xml” 显示了如何经过xml定义的方式来给例 2.15 “Car”中的类Car 以及例 2.19 “RentalCar with @GroupSequence” 中的RentalCar定义约束条件.

例 4.4. constraints-car.xml

<constraint-mappings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"                      xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.0.xsd"                      xmlns="http://jboss.org/xml/ns/javax/validation/mapping">     <default-package>org.hibernate.validator.quickstart</default-package>     <bean class="Car" ignore-annotations="true">         <field name="manufacturer">             <constraint annotation="javax.validation.constraints.NotNull"/>         </field>         <field name="licensePlate">             <constraint annotation="javax.validation.constraints.NotNull"/>         </field>         <field name="seatCount">             <constraint annotation="javax.validation.constraints.Min">                 <element name="value">2</element>             </constraint>         </field>         <field name="driver">             <valid/>         </field>         <getter name="passedVehicleInspection" ignore-annotations="true">             <constraint annotation="javax.validation.constraints.AssertTrue">                 <message>The car has to pass the vehicle inspection first</message>                 <groups>                     <value>CarChecks</value>                 </groups>                 <element name="max">10</element>             </constraint>         </getter>     </bean>     <bean class="RentalCar" ignore-annotations="true">         <class ignore-annotations="true">             <group-sequence>                 <value>RentalCar</value>                 <value>CarChecks</value>             </group-sequence>         </class>     </bean>     <constraint-definition annotation="org.mycompany.CheckCase" include-existing-validator="false">         <validated-by include-existing-validators="false">             <value>org.mycompany.CheckCaseValidator</value>         </validated-by>     </constraint-definition> </constraint-mappings>

这个xml的定义基本上和经过编程方式差很少, 因此只须要简单的解释. 其中default-package属性用来定义一个默认的包路径, 若是下面指定的class不是全限定名称的话,会自动加上这个默认的包路径. 每一个xml文件均可以包含任意多个bean节点, 每一个对应一个要添加约束条件的实体类.

警告

每一个实体类只能在全部的xml映射文件中被定义一次, 不然会抛出异常.

经过添加ignore-annotations 属性并将其设置为true能够忽略在对应bean上添加的约束标注信息, 这个属性的默认值就是trueignore-annotations 属性还能够定义在class, fields 和 getter属性上, 若是没有明确指定的话, 那么默认级别是bean (可参考 第 2.1 节 “定义约束”). constraint 节点用于添加一个约束条件到其父节点对应的元素上, 而且它须要经过annotation属性来指定须要使用哪一个约束条件. 对于每一个约束条件中所须要的属性, 其中, 由Bean Validation 规范规定的属性(message, groups 和 payload) 能够经过同名的子节点来定义, 而每一个约束条件中自定义的属性, 则须要使用element节点来定义.

class节点一样支持经过group-sequence节点来对一个类的默认校验组进行重定义(请参考 第 2.3.2 节 “对一个类重定义其默认校验组”) .

最后, 你还能够经过constraint-definition节点来对一个指定的约束条件上绑定的校验器(ConstraintValidator)进行修改. 此节点上的annotation对应要修改的约束条件, 而validated-by子节点中(按顺序)列出要关联到此约束条件上的校验器( ConstraintValidator的实现类), 而include-existing-validator属性若是是false的话,那么默认定义在此约束条件上的校验器将被忽略, 若是为true, 那么在xml中定义的校验器会被添加在约束条件上默认定义的校验器的后面.

第 5 章 Bootstrapping

第 5.1 节 “Configuration 和 ValidatorFactory”中咱们说道过, 最简单的建立一个Validator实例的方法是经过Validation.buildDefaultValidatorFactory. 在本章中咱们会继续介绍javax.validation.Validation中的其余方法, 以及如何经过这些方法在Bean Validation初始化的时候对其进行配置的.

The different bootstrapping options allow, amongst other things, to bootstrap any Bean Validation implementation on the classpath. 一般, 一个服务的提供者是可以被Java Service Provider发现的. 对于Bean Validation的实现(服务提供者)来讲, 他们的META-INF/services目录下须要包含一个名为javax.validation.spi.ValidationProvider的文件. 此文件中包含了一个ValidationProvider接口的实现类的全路径名称, 具体到Hibernate Validator来讲, 就是org.hibernate.validator.HibernateValidator.

注意

若是当前类路径下存在多个Bean Validation的实现, 那么Validation.buildDefaultValidatorFactory()并不能保证具体那个实现会被使用. 若是想指定某一个的话, 请使用Validation.byProvider().

5.1. Configuration 和 ValidatorFactory

Validation类提供了三种方法来建立一个Validator的实例, 例 5.1 “Validation.buildDefaultValidatorFactory()”中显示的是最简单的方法.

例 5.1. Validation.buildDefaultValidatorFactory()

ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator();

你也能够经过Validation.byDefaultProvider()现获取一个Configuration对象, 这样能够对要建立的Validator进行配置.

例 5.2. Validation.byDefaultProvider()

Configuration<?> config = Validation.byDefaultProvider().configure(); config.messageInterpolator(new MyMessageInterpolator())     .traversableResolver( new MyTraversableResolver())     .constraintValidatorFactory(new MyConstraintValidatorFactory()); ValidatorFactory factory = config.buildValidatorFactory(); Validator validator = factory.getValidator(); 

MessageInterpolatorTraversableResolver 和 ConstraintValidatorFactory会在后面详细介绍.

最后, 你能够指定使用哪一个Bean Validation的实现. 若是类路径下存在多个Bean Validation的实现的话,这样就颇有必要了. 例如, 若是你想使用Hibernate Validator来做为内部实现来建立Validator的话:

例 5.3. Validation.byProvider( HibernateValidator.class )

HibernateValidatorConfiguration config = Validation.byProvider( HibernateValidator.class ).configure(); config.messageInterpolator(new MyMessageInterpolator())     .traversableResolver( new MyTraversableResolver())     .constraintValidatorFactory(new MyConstraintValidatorFactory()); ValidatorFactory factory = config.buildValidatorFactory(); Validator validator = factory.getValidator();

提示

建立出来的Validator实例是线程安全的, 因此你能够把它缓存起来.

5.2. ValidationProviderResolver

若是 Java Service Provider机制在你的环境中不可以正常工做, 或者你有特别的classloader设置的话, 你也能够提供一个自定义的ValidationProviderResolver.例 5.4 “使用自定义的ValidationProviderResolver”显示了如何在OSGi环境中插入自定义的provider resolver.

例 5.4. 使用自定义的ValidationProviderResolver

Configuration<?> config = Validation.byDefaultProvider()     .providerResolver( new OSGiServiceDiscoverer() )     .configure(); ValidatorFactory factory = config.buildValidatorFactory(); Validator validator = factory.getValidator(); 

在这种状况下, 你的OSGiServiceDiscoverer类须要实现ValidationProviderResolver接口:

例 5.5. ValidationProviderResolver接口

public interface ValidationProviderResolver {     /**      * Returns a list of ValidationProviders available in the runtime environment.      *      * @return list of validation providers.        */     List<ValidationProvider<?>> getValidationProviders(); } 

5.3. MessageInterpolator

第 2.2.4 节 “验证失败提示信息解析” already discussed the default message interpolation algorithm. If you have special requirements for your message interpolation you can provide a custom interpolator using Configuration.messageInterpolator(). This message interpolator will be shared by all validators generated by the ValidatorFactory created from this Configuration例 5.6 “自定义的MessageInterpolator” shows an interpolator (available in Hibernate Validator) which can interpolate the value being validated in the constraint message. To refer to this value in the constraint message you can use:

  • ${validatedValue}: this will call String.valueOf on the validated value.

  • ${validatedValue:<format>}: provide your own format string which will be passed to String.format together with the validated value. Refer to the javadoc of String.format for more information about the format options.

例 5.6. 自定义的MessageInterpolator

Configuration<?> configuration = Validation.byDefaultProvider().configure(); ValidatorFactory factory = configuration     .messageInterpolator(new ValueFormatterMessageInterpolator(configuration.getDefaultMessageInterpolator()))     .buildValidatorFactory(); Validator validator = factory.getValidator(); 

提示

It is recommended that MessageInterpolator implementations delegate final interpolation to the Bean Validation default MessageInterpolator to ensure standard Bean Validation interpolation rules are followed. The default implementation is accessible through Configuration.getDefaultMessageInterpolator().

5.3.1. ResourceBundleLocator

一个广泛的需求是你可能须要为错误消息解析指定你本身的resource bundles. ResourceBundleMessageInterpolator是Hibernate Validator中默认的MessageInterpolator的实现, 它默认状况下是经过ResourceBundle.getBundle来获取resource bundle的. 不过, ResourceBundleMessageInterpolator也支持你指定一个自定义的ResourceBundleLocator实现来提供你本身的resource bundle. 例 5.7 “自定义的ResourceBundleLocator”提供了一个示例. 在这个例子中, 先经过 HibernateValidatorConfiguration.getDefaultResourceBundleLocator获取默认的ResourceBundleLocator实现, 而后再用你自定义的实现把默认的包装起来, 代理模式.

例 5.7. 自定义的ResourceBundleLocator

HibernateValidatorConfiguration configure = Validation.byProvider(HibernateValidator.class).configure(); ResourceBundleLocator defaultResourceBundleLocator = configure.getDefaultResourceBundleLocator();  ResourceBundleLocator myResourceBundleLocator = new MyCustomResourceBundleLocator(defaultResourceBundleLocator); configure.messageInterpolator(new ResourceBundleMessageInterpolator(myResourceBundleLocator)); 

Hibernate Validator提供了两个ResourceBundleLocator的实现 - PlatformResourceBundleLocator (默认) 和 AggregateResourceBundleLocator. 后者能够定义一系列的resource bundle, 而后它会读取这些文件, 而且把它们组合成一个. 更多信息请参考此类的javadoc 文档.

5.4. TraversableResolver

到目前位置咱们尚未讨论过TraversableResolver接口, 它的设计目的是在某些状况下, 咱们可能不该该去获取一个属性的状态. 最典型的状况就是一个延迟加载的属性或者与JPA中涉及到关联关系的时候. 当验证这两种状况的属性的时候, 极可能会触发一次对数据库的查询.Bean Validation正是经过TraversableResolver接口来控制可否访问某一个属性的 (例 5.8 “TraversableResolver接口”).

例 5.8. TraversableResolver接口

/**
 * Contract determining if a property can be accessed by the Bean Validation provider  * This contract is called for each property that is being either validated or cascaded.  *  * A traversable resolver implementation must be thread-safe.  *  */ public interface TraversableResolver {     /**      * Determine if the Bean Validation provider is allowed to reach the property state      *      * @param traversableObject object hosting <code>traversableProperty</code> or null        *                          if validateValue is called      * @param traversableProperty the traversable property.      * @param rootBeanType type of the root object passed to the Validator.      * @param pathToTraversableObject path from the root object to      *        <code>traversableObject</code>      *        (using the path specification defined by Bean Validator).      * @param elementType either <code>FIELD</code> or <code>METHOD</code>.      *      * @return <code>true</code> if the Bean Validation provider is allowed to      *         reach the property state, <code>false</code> otherwise.      */      boolean isReachable(Object traversableObject,                          Path.Node traversableProperty,                          Class<?> rootBeanType,                          Path pathToTraversableObject,                          ElementType elementType);     /**      * Determine if the Bean Validation provider is allowed to cascade validation on      * the bean instance returned by the property value      * marked as <code>@Valid</code>.      * Note that this method is called only if isReachable returns true for the same set of      * arguments and if the property is marked as <code>@Valid</code>      *      * @param traversableObject object hosting <code>traversableProperty</code> or null      *                          if validateValue is called      * @param traversableProperty the traversable property.      * @param rootBeanType type of the root object passed to the Validator.      * @param pathToTraversableObject path from the root object to      *        <code>traversableObject</code>      *        (using the path specification defined by Bean Validator).      * @param elementType either <code>FIELD</code> or <code>METHOD</code>.      *      * @return <code>true</code> if the Bean Validation provider is allowed to      *         cascade validation, <code>false</code> otherwise.      */      boolean isCascadable(Object traversableObject,                           Path.Node traversableProperty,                           Class<?> rootBeanType,                           Path pathToTraversableObject,                           ElementType elementType); } 

Hibernate Validator provides two TraversableResolvers out of the box which will be enabled automatically depending on your environment. The first is the DefaultTraversableResolver which will always return true for isReachable() and isTraversable(). The second is the JPATraversableResolver which gets enabled when Hibernate Validator gets used in combination with JPA 2. In case you have to provide your own resolver you can do so again using the Configuration object as seen in 例 5.9 “自定义的TraversableResolver”.

例 5.9. 自定义的TraversableResolver

Configuration<?> configuration = Validation.byDefaultProvider().configure(); ValidatorFactory factory = configuration     .traversableResolver(new MyTraversableResolver())     .buildValidatorFactory(); Validator validator = factory.getValidator(); 

5.5. ConstraintValidatorFactory

最后, 还有个配置项得提一下, 那就是ConstraintValidatorFactory类. Hibernate Validator中默认的ConstraintValidatorFactory须要一个无参的构造方法来初始化ConstraintValidator的实例(参考第 3.1.2 节 “约束校验器”). 对于自定义的ConstraintValidatorFactory实现来讲, 例如, 你可让其支持对约束条件的依赖注入等功能. 配置使用这个自定义的ConstraintValidatorFactory的方法仍是老样子(例 5.10 “自定义的ConstraintValidatorFactory”).

例 5.10. 自定义的ConstraintValidatorFactory

Configuration<?> configuration = Validation.byDefaultProvider().configure(); ValidatorFactory factory = configuration     .constraintValidatorFactory(new IOCConstraintValidatorFactory())     .buildValidatorFactory(); Validator validator = factory.getValidator(); 

你须要实现此接口:

例 5.11. ConstraintValidatorFactory接口

public interface ConstraintValidatorFactory {     /**      * @param key The class of the constraint validator to instantiate.      *      * @return A constraint validator instance of the specified class.      */      <extends ConstraintValidator<?,?>> T getInstance(Class<T> key); } 

警告

若是一个约束条件的实现须要依赖ConstraintValidatorFactory的某个特定的行为(例如依赖注入或者没有无参的构造方法等) 均可能致使不可移植.

注意

ConstraintValidatorFactory不该该缓存其建立的实例, 由于每一个实例均可能在其的初始化方法中被修改.

第 6 章 Metadata API

The Bean Validation specification provides not only a validation engine, but also a metadata repository for all defined constraints. The following paragraphs are discussing this API. All the introduced classes can be found in the javax.validation.metadata package.

6.1. BeanDescriptor

The entry into the metadata API is via Validator.getConstraintsForClass which returns an instance of the BeanDescriptor interface. Using this bean descriptor you can determine whether the specified class hosts any constraints at all via beanDescriptor.isBeanConstrained.

提示

If a constraint declaration hosted by the requested class is invalid, a ValidationException is thrown.

You can then call beanDescriptor.getConstraintDescriptors to get a set of ConstraintDescriptors representing all class level constraints.

If you are interested in property level constraints, you can call beanDescriptor.getConstraintsForProperty or beanDescriptor.getConstrainedProperties to get a single resp. set of PropertyDescriptors (see 第 6.2 节 “PropertyDescriptor”).

6.2. PropertyDescriptor

The PropertyDescriptor interface extends the ElementDescriptor interface and represents constraints on properties of a class. The constraint can be declared on the attribute itself or on the getter of the attribute - provided Java Bean naming conventions are respected. A PropertyDescriptor adds isCascaded (returning true if the property is marked with @Valid) and getPropertyName to the ElementDescriptor functionality.

6.3. ElementDescriptor

The ElementDiscriptor interface is the common base class for BeanDescriptor and PropertyDescriptor. Next to the hasConstraints and getConstraintDescriptors methods it also offers access to the ConstraintFinder API which allows you to query the metadata API in a more fine grained way. For example you can restrict your search to constraints described on fields or on getters or a given set of groups. Given an ElementDescriptor instance you just call findConstraints to retrieve a ConstraintFinder instance.

例 6.1. Usage of ConstraintFinder

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
BeanDescriptor beanDescriptor = validator.getConstraintsForClass(Person.class);
PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty("name");
Set<ConstraintDescriptor<?>> constraints = propertyDescriptor.findConstraints()
                                           .declaredOn(ElementType.METHOD)
                                           .unorderedAndMatchingGroups(Default.class)
                                           .lookingAt(Scope.LOCAL_ELEMENT)
                                           .getConstraintDescriptors();

例 6.1 “Usage of ConstraintFinder” shows an example on how to use the ConstraintFinder API. Interesting are especially the restrictions unorderedAndMatchingGroups and lookingAt(Scope.LOCAL_ELEMENT). The former allows to only return ConstraintDescriptors matching a specified set of groups wheras the latter allows to distinguish between constraint directly specified on the element (Scope.LOCAL_ELEMENT) or constraints belonging to the element but hosted anywhere in the class hierarchy (Scope.HIERARCHY).

警告

Order is not respected by unorderedAndMatchingGroups, but group inheritance and inheritance via sequence are.

6.4. ConstraintDescriptor

Last but not least, the ConstraintDescriptor interface describes a single constraint together with its composing constraints. Via an instance of this interface you get access to the constraint annotation and its parameters, as well as the groups the constraint is supposed to be applied on. It also also you to access the pass-through constraint payload (see 例 3.2 “定义一个CheckCase的约束标注”).

第 7 章 与其余框架集成

Hibernate Validator 的设计初衷是在一个分层的应用程序中, 约束信息只须要被定义一次( 经过在领域模型上标注), 而后在不一样的层中进行数据校验.

7.1. OSGi

The Hibernate Validator jar file is conform to the OSGi specification and can be used within any OSGi container. The following lists represent the packages imported and exported by Hibernate Validator. The classes within the exported packages are considered part of Hibernate Validator public API.

提示

The Java Service Provider mechanism used by Bean Validation to automatically discover validation providers doesn't work in an OSGi environment. To solve this, you have to provide a custom ValidationProviderResolver. See 第 5.2 节 “ValidationProviderResolver”

Exported packages

  • org.hibernate.validator

  • org.hibernate.validator.constraints

  • org.hibernate.validator.cfg

  • org.hibernate.validator.cfg.context

  • org.hibernate.validator.cfg.defs

  • org.hibernate.validator.group

  • org.hibernate.validator.messageinterpolation

  • org.hibernate.validator.method

  • org.hibernate.validator.method.metadata

  • org.hibernate.validator.resourceloading

Imported packages

  • javax.persistence.*, [2.0.0,3.0.0), optional

  • javax.validation.*, [1.0.0,2.0.0)

  • javax.xml.*

  • org.xml.sax.*

  • org.slf4j.*, [1.5.6,2.0.0)

  • org.joda.time.*, [1.6.0,2.0.0), optional

  • org.jsoup.*, [1.5.2,2.0.0), optional

7.2. 与数据库集成校验

Hibernate Annotations (即 Hibernate 3.5.x) 会自动的把你定已在实体模型上的约束信息添加到其映射信息中. 例如, 假设你的一个实体类的属性上有@NotNull的约束的话, 那么Hibernate在生成建立此实体对应的表的DDL的时候, 会自动的给那个属性对应的字段添加上not null.

若是由于某种缘由, 你不想使用这个特性, 那么能够将hibernate.validator.apply_to_ddl属性设置为false. 请参考???.

你也能够限制这个DDL约束自动生成的特性只应用到一部分实体类. 只须要设置org.hibernate.validator.group.ddl属性, 这个属性的值是你想要应用此特性的实体类的全路径名称, 每一个以逗号分隔.

7.3. ORM集成

Hibernate Validator不只可以和Hibernate集成工做, 还可以和任何JPA的实现很好的一块儿工做.

提示

When lazy loaded associations are supposed to be validated it is recommended to place the constraint on the getter of the association. Hibernate replaces lazy loaded associations with proxy instances which get initialized/loaded when requested via the getter. If, in such a case, the constraint is placed on field level the actual proxy instance is used which will lead to validation errors.

7.3.1. 基于Hibernate事件模型的校验

Hibernate Annotations (即 Hibernate 3.5.x) 中包含了一个的Hibernate 事件监听器(译注: 请阅读Hibernate Core文档了解Hibernate的事件模型) - org.hibernate.cfg.beanvalidation.BeanValidationEventListener - 来为Hibernate Validator服务. 当一个PreInsertEventPreUpdateEvent 或 PreDeleteEvent事件发生的时候, 这个监听器就能够对该事件所涉及到的实体对象进行校验, 若是校验不经过的话, 则抛出异常. 默认状况下, Hibernate在对每一个对象进行保存或者修改操做的时候,都会对其进行校验, 而删除操做则不会. 你能够经过javax.persistence.validation.group.pre-persist, javax.persistence.validation.group.pre-update 和 javax.persistence.validation.group.pre-remove属性来定义对应事件发生的时候, 具体要校验哪(些)个校验组, 这个属性的值是要应用的校验组类的全路径, 使用逗号分隔. 例 7.1 “自定义BeanValidationEvenListener”显示了这几个属性在Hibernate内部定义的默认值, 因此, 你不须要在你的应用中再重复定义了.

若是发生了违反约束条件的状况, 该监听器会抛出一个运行时的ConstraintViolationException异常, 此异常包含了一系列的ConstraintViolation对象用于描述每一个违反了约束条件的状况.

若是类路径上有Hibernate Validator, 则Hibernate Annotations (或 Hibernate EntityManager)会自动调用它, 若是你想避免这种状况, 能够设置javax.persistence.validation.mode属性为none.

注意

若是实体模型上没有定义约束条件, 则不会有任何性能损耗.

若是你想在Hibernate Core中使用上面提到的事件监听器, 则能够在hibernate.cfg.xml中定义以下的配置信息:

例 7.1. 自定义BeanValidationEvenListener

<hibernate-configuration>     <session-factory>        ...        <property name="javax.persistence.validation.group.pre-persist">javax.validation.groups.Default</property>        <property name="javax.persistence.validation.group.pre-update">javax.validation.groups.Default</property>        <property name="javax.persistence.validation.group.pre-remove"></property>        ...        <event type="pre-update">          <listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>        </event>        <event type="pre-insert">          <listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>        </event>        <event type="pre-delete">          <listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>        </event>     </session-factory> </hibernate-configuration>

7.3.2. JPA

If you are using JPA 2 and Hibernate Validator is in the classpath the JPA2 specification requires that Bean Validation gets enabled. The properties javax.persistence.validation.group.pre-persist, javax.persistence.validation.group.pre-update and javax.persistence.validation.group.pre-remove as described in 第 7.3.1 节 “基于Hibernate事件模型的校验” can in this case be configured in persistence.xmlpersistence.xml also defines a node validation-mode which can be set to AUTOCALLBACKNONE. The default is AUTO.

对于JPA1来说, 你须要本身建立和注册Hibernate Validator. 若是你是使用Hibernate EntityManager, 那么你能够把第 7.3.1 节 “基于Hibernate事件模型的校验”中列出来的BeanValidationEventListener类添加到你的项目中, 而后再手工注册它.

7.4. 展现层校验

若是你正在使用JSF2或者JBoss Seam™,而且Hibernate Validator (Bean Validation) 在类路径上的话, 那么界面上的字段能够被自动校验. 例 7.2 “在JSF2中使用Bean Validation”显示了一个在JSF页面上使用f:validateBean标签的实例. 更多的信息请参考Seam的文档或者JSF2规范.

例 7.2. 在JSF2中使用Bean Validation

<h:form>
  <f:validateBean>
    <h:inputText value=”#{model.property}” />
    <h:selectOneRadio value=”#{model.radioProperty}” > ... </h:selectOneRadio>
    <!-- other input components here -->
  </f:validateBean> </h:form> 

提示

The integration between JSF 2 and Bean Validation is described in the "Bean Validation Integration" chapter of JSR-314. It is interesting to know that JSF 2 implements a custom MessageInterpolator to ensure ensure proper localization. To encourage the use of the Bean Validation message facility, JSF 2 will per default only display the generated Bean Validation message. This can, however, be configured via the application resource bundle by providing the following configuration ({0} is replaced with the Bean Validation message and {1} is replaced with the JSF component label):

javax.faces.validator.BeanValidator.MESSAGE={1}: {0}

The default is:

javax.faces.validator.BeanValidator.MESSAGE={0}

第 8 章 Hibernate Validator Specifics

In the following sections we are having a closer look at some of the Hibernate Validator specific features (features which are not part of the Bean Validation specification). This includes the fail fast mode, the programmatic constraint configuration API and boolean composition of composing constraints.

注意

The features described in the following sections are not portable between Bean Validation providers/implementations.

8.1. Public API

Let's start, however, with a look at the public API of Hibernate Validator. 表 8.1 “Hibernate Validator public API” lists all packages belonging to this API and describes their purpose.

Any packages not listed in that table are internal packages of Hibernate Validator and are not intended to be accessed by clients. The contents of these internal packages can change from release to release without notice, thus possibly breaking any client code relying on it.

注意

In the following table, when a package is public its not necessarily true for its nested packages.

表 8.1. Hibernate Validator public API

Packages Description
org.hibernate.validator This package contains the classes used by the Bean Validation bootstrap mechanism (eg. validation provider, configuration class). For more details see 第 5 章 Bootstrapping.
org.hibernate.validator.cfg, org.hibernate.validator.cfg.context, org.hibernate.validator.cfg.defs With Hibernate Validator you can define constraints via a fluent API. These packages contain all classes needed to use this feature. In the package org.hibernate.validator.cfg you will find the ConstraintMapping class and in package org.hibernate.validator.cfg.defs all constraint definitions. For more details see 第 8.4 节 “Programmatic constraint definition”.
org.hibernate.validator.constraints In addition to Bean Validation constraints, Hibernate Validator provides some useful custom constraints. This package contains all custom annotation classes. For more details see 第 2.4.2 节 “Additional constraints”.
org.hibernate.validator.group With Hibernate Validator you can define dynamic default group sequences in function of the validated object state. This package contains all classes needed to use this feature (GroupSequenceProvider annotation and DefaultGroupSequenceProvider contract). For more details see 第 2.3.2 节 “对一个类重定义其默认校验组”.
org.hibernate.validator.messageinterpolation, org.hibernate.validator.resourceloading These packages contain the classes related to constraint message interpolation. The first package contains two implementations of MessageInterpolator. The first one, ValueFormatterMessageInterpolator allows to interpolate the validated value into the constraint message, see 第 5.3 节 “MessageInterpolator”. The second implementation named ResourceBundleMessageInterpolator is the implementation used by default by Hibernate Validator. This implementation relies on a ResourceBundleLocator, see 第 5.3.1 节 “ResourceBundleLocator”. Hibernate Validator provides different ResourceBundleLocator implementations located in the package org.hibernate.validator.resourceloading.
org.hibernate.validator.method, org.hibernate.validator.method.metadata Hibernate Validator provides support for method-level constraints based on appendix C of the Bean Validation specification. The first package contains the MethodValidator interface allowing you to validate method return values and parameters. The second package contains meta data for constraints hosted on parameters and methods which can be retrieved via the MethodValidator.

8.2. Fail fast mode

First off, the fail fast mode. Hibernate Validator allows to return from the current validation as soon as the first constraint violation occurs. This is called the fail fast mode and can be useful for validation of large object graphs where one is only interested whether there is a constraint violation or not. 例 8.1 “Enabling failFast via a property”例 8.2 “Enabling failFast at the Configuration level” and 例 8.3 “Enabling failFast at the ValidatorFactory level” show multiple ways to enable the fail fast mode.

例 8.1. Enabling failFast via a property

HibernateValidatorConfiguration configuration = Validation.byProvider( HibernateValidator.class ).configure();
ValidatorFactory factory = configuration.addProperty( "hibernate.validator.fail_fast", "true" ).buildValidatorFactory();
Validator validator = factory.getValidator();

// do some actual fail fast validation
...

例 8.2. Enabling failFast at the Configuration level

HibernateValidatorConfiguration configuration = Validation.byProvider( HibernateValidator.class ).configure();
ValidatorFactory factory = configuration.failFast( true ).buildValidatorFactory();
Validator validator = factory.getValidator();

// do some actual fail fast validation
...

例 8.3. Enabling failFast at the ValidatorFactory level

HibernateValidatorConfiguration configuration = Validation.byProvider( HibernateValidator.class ).configure();
ValidatorFactory factory = configuration.buildValidatorFactory();

Validator validator = factory.getValidator();

// do some non fail fast validation
...

validator = factory.unwrap( HibernateValidatorFactory.class )
            .usingContext()
            .failFast( true )
            .getValidator();

// do fail fast validation
...

8.3. Method validation

The Bean Validation API allows to specify constraints for fields, properties and types. Hibernate Validator goes one step further and allows to place contraint annotations also on method parameters and method return values, thus enabling a programming style known as "Programming by Contract".

More specifically this means that Bean Validation constraints can be used to specify

  • the preconditions that must be met before a method invocation (by annotating method parameters with constraints) and

  • the postconditions that are guaranteed after a method invocation (by annotating methods)

This approach has several advantages over traditional ways of parameter and return value checking:

  • The checks don't have to be performed manually (e.g. by throwing IllegalArgumentExceptions or similar), resulting in less code to write and maintain.

  • A method's pre- and postconditions don't have to be expressed again in the method's JavaDoc, since the constraint annotations will automatically be included in the generated JavaDoc. This avoids redundancy and reduces the chance of inconsistencies between implementation and documentation.

注意

Method validation was also considered to be included in the Bean Validation API as defined by JSR 303, but it didn't become part of the 1.0 version. A basic draft is outlined in appendix C of the specification, and the implementation in Hibernate Validator is largely influenced by this draft. The feature is considered again for inclusion in BV 1.1.

8.3.1. Defining method-level constraints

例 8.4 “Using method-level constraints” demonstrates the definition of method-level constraints.

例 8.4. Using method-level constraints

public class RentalStation {

    @NotNull 
    public Car rentCar(@NotNull Customer customer, @NotNull @Future Date startDate, @Min(1) int durationInDays) { 
        //...
    }
}

 

Here the following pre- and postconditions for the rentCar() method are declared:

  • The renting customer may not be null

  • The rental's start date must not be null and must be in the future

  • The rental duration must be at least one day

  • The returned Car instance may not be null

Using the @Valid annotation it's also possible to define that a cascaded validation of parameter or return value objects shall be performed. An example can be found in 例 8.5 “Cascaded validation of method-level constraints”.

例 8.5. Cascaded validation of method-level constraints

public class RentalStation {

    @Valid
    public Set<Rental> getRentalsByCustomer(@Valid Customer customer) { 
        //...
    }
}

Here all the constraints declared at the Customer type will be evaluated when validating the method parameter and all constraints declared at the returned Rental objects will be evaluated when validating the method's return value.

8.3.1.1. Using method constraints in type hierarchies

Special care must be taken when defining parameter constraints in inheritance hierarchies.

When a method is overridden in sub-types method parameter constraints can only be declared at the base type. The reason for this restriction is that the preconditions to be fulfilled by a type's client must not be strengthened in sub-types (which may not even be known to the base type's client). Note that also if the base method doesn't declare any parameter constraints at all, no parameter constraints may be added in overriding methods.

The same restriction applies to interface methods: no parameter constraints may be defined at the implementing method (or the same method declared in sub-interfaces).

If a violation of this rule is detected by the validation engine, a javax.validation.ConstraintDeclarationException will be thrown. In 例 8.6 “Illegal parameter constraint declarations” some examples for illegal parameter constraints declarations are shown.

例 8.6. Illegal parameter constraint declarations

public class Car {

    public void drive(Person driver) { ... }

}

public class RentalCar extends Car {

    //not allowed, parameter constraint added in overriding method
    public void drive(@NotNull Person driver) { ... }

}

public interface ICar {

    void drive(Person driver);

}

public class CarImpl implements ICar {

    //not allowed, parameter constraint added in implementation of interface method
    public void drive(@NotNull Person driver) { ... }

}

This rule only applies to parameter constraints, return value constraints may be added in sub-types without any restrictions as it is alright to strengthen the postconditions guaranteed to a type's client.

8.3.2. Evaluating method-level constraints

To validate method-level constraints Hibernate Validator provides the interface org.hibernate.validator.method.MethodValidator.

As shown in 例 8.7 “The MethodValidator interface” this interface defines methods for the evaluation of parameter as well as return value constraints and for retrieving an extended type descriptor providing method constraint related meta data.

例 8.7. The MethodValidator interface

public interface MethodValidator {

    <T> Set<MethodConstraintViolation<T>> validateParameter(T object, Method method, Object parameterValue, int parameterIndex, Class<?>... groups);
    
    <T> Set<MethodConstraintViolation<T>> validateAllParameters(T object, Method method, Object[] parameterValues, Class<?>... groups);
    
    <T> Set<MethodConstraintViolation<T>> validateReturnValue(T object, Method method, Object returnValue, Class<?>... groups);
 
    TypeDescriptor getConstraintsForType(Class<?> clazz);
}    

To retrieve a method validator get hold of an instance of HV's javax.validation.Validator implementation and unwrap it to MethodValidator as shown in 例 8.8 “Retrieving a MethodValidator instance”.

例 8.8. Retrieving a MethodValidator instance

MethodValidator methodValidator = Validation.byProvider( HibernateValidator.class )
    .configure()
    .buildValidatorFactory()
    .getValidator()
    .unwrap( MethodValidator.class ); 

The validation methods defined on MethodValidator each return a Set<MethodConstraintViolation>. The type MethodConstraintViolation (see 例 8.9 “The MethodConstraintViolation type”) extends javax.validation.ConstraintViolation and provides additional method level validation specific information such as the method and index of the parameter which caused the constraint violation.

例 8.9. The MethodConstraintViolation type

public interface MethodConstraintViolation<T> extends ConstraintViolation<T> {
    
    public static enum Kind { PARAMETER, RETURN_VALUE }

    Method getMethod();

    Integer getParameterIndex();

    String getParameterName();

    Kind getKind();
}

注意

The method getParameterName() currently returns synthetic parameter identifiers such as "arg0", "arg1" etc. In a future version of Hibernate Validator support for specifying parameter identifiers might be added.

Typically the validation of method-level constraints is not invoked manually but automatically upon method invocation by an integration layer using AOP (aspect-oriented programming) or similar method interception facilities such as the JDK's java.lang.reflect.Proxy API or CDI ("JSR 299: Contexts and Dependency Injection for the JavaTM EE platform").

If a parameter or return value constraint can't be validated sucessfully such an integration layer typically will throw a MethodConstraintViolationException which similar to javax.validation.ConstraintViolationException contains a set with the occurred constraint violations.

提示

If you are using CDI you might be interested in the Seam Validation project. This Seam module provides an interceptor which integrates the method validation functionality with CDI.

8.3.3. Retrieving method-level constraint meta data

As outlined in 第 6 章 Metadata API the Bean Validation API provides rich capabilities for retrieving constraint related meta data. Hibernate Validator extends this API and allows to retrieve constraint meta data also for method-level constraints.

例 8.10 “Retrieving meta data for method-level constraints” shows how to use this extended API to retrieve constraint meta data for the rentCar() method from the RentalStation type.

例 8.10. Retrieving meta data for method-level constraints

TypeDescriptor typeDescriptor = methodValidator.getConstraintsForType(RentalStation.class)

//retrieve a descriptor for the rentCar() method
MethodDescriptor rentCarMethod = typeDescriptor.getConstraintsForMethod("rentCar", Customer.class, Date.class, int.class);
assertEquals(rentCarMethod.getMethodName(), "rentCar");
assertTrue(rentCarMethod.hasConstraints());
assertFalse(rentCarMethod.isCascaded());

//retrieve constraints from the return value
Set<ConstraintDescriptor<?>> returnValueConstraints = rentCarMethod.findConstraints().getConstraintDescriptors();
assertEquals(returnValueConstraints.size(), 1);
assertEquals(returnValueConstraints.iterator().next().getAnnotation().annotationType(), NotNull.class);

List<ParameterDescriptor> allParameters = rentCarMethod.getParameterDescriptors();
assertEquals(allParameters.size(), 3);

//retrieve a descriptor for the startDate parameter
ParameterDescriptor startDateParameter = allParameters.get(1);
assertEquals(startDateParameter.getIndex(), 1);
assertFalse(startDateParameter.isCascaded());
assertEquals(startDateParameter.findConstraints().getConstraintDescriptors().size(), 2);

Refer to the JavaDoc of the package org.hibernate.validator.method.metadata for more details on the extended meta data API.

8.4. Programmatic constraint definition

Another addition to the Bean Validation specification is the ability to configure constraints via a fluent API. This API can be used exclusively or in combination with annotations and xml. If used in combination programmatic constraints are additive to constraints configured via the standard configuration capabilities.

The API is centered around the ConstraintMapping class which can be found in the package org.hibernate.validator.cfg. Starting with the instantiation of a new ConstraintMapping, constraints can be defined in a fluent manner as shown in 例 8.11 “Programmatic constraint definition”.

例 8.11. Programmatic constraint definition

ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Car.class )
    .property( "manufacturer", FIELD )
        .constraint( new NotNullDef() )
    .property( "licensePlate", FIELD )
        .constraint( new NotNullDef() )
        .constraint( new SizeDef().min( 2 ).max( 14 ) )
    .property( "seatCount", FIELD )
        .constraint( new MinDef()value ( 2 ) )
.type( RentalCar.class )
    .property( "rentalStation", METHOD )
        .constraint( new NotNullDef() );      

 

As you can see constraints can be configured on multiple classes and properties using method chaining. The constraint definition classes NotNullDefSizeDef and MinDef are helper classes which allow to configure constraint parameters in a type-safe fashion. Definition classes exist for all built-in constraints in the org.hibernate.validator.cfg.defs package.

For custom constraints you can either create your own definition classes extending ConstraintDef or you can use GenericConstraintDef as seen in 例 8.12 “Programmatic constraint definition using createGeneric()”.

例 8.12. Programmatic constraint definition using createGeneric()

ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Car.class )
    .property( "licensePlate", FIELD )
        .constraint( new GenericConstraintDef<CheckCase.class>( CheckCase.class ).param( "value", CaseMode.UPPER ) );   

 

Not only standard class- and property-level constraints but also method constraints can be configured using the API. As shown in 例 8.13 “Programmatic definition of method constraints” methods are identified by their name and their parameters (if there are any). Having selected a method, constraints can be placed on the method's parameters and/or return value.

例 8.13. Programmatic definition of method constraints

ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Car.class )
    .method( "drive", String.class, Integer.class )
        .parameter( 0 )
            .constraint( new NotNullDef() )
            .constraint( new MinDef().value ( 1 ) )
        .parameter( 1 )
            .constraint( new NotNullDef() )
        .returnValue()
            .constraint( new NotNullDef() )
    .method( "check" )
        .returnValue()
            .constraint( new NotNullDef() );      

Using the API it's also possible to mark properties, method parameters and method return values as cascading (equivalent to annotating them with @Valid). An example can be found in 例 8.14 “Marking constraints for cascaded validation”.

例 8.14. Marking constraints for cascaded validation

ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Car.class )
    .property( "manufacturer", FIELD )
        .valid()
    .property( "licensePlate", METHOD )
        .valid()
    .method( "drive", String.class, Integer.class )
        .parameter( 0 )
            .valid()
        .parameter( 1 )
            .valid()
        .returnValue()
            .valid()
.type( RentalCar.class )
    .property( "rentalStation", METHOD )
        .valid();

 

Last but not least you can configure the default group sequence or the default group sequence provider of a type as shown in 例 8.15 “Configuration of default group sequence and default group sequence provider”.

例 8.15. Configuration of default group sequence and default group sequence provider

ConstraintMapping mapping = new ConstraintMapping();
mapping.type( Car.class )
    .defaultGroupSequence( Car.class, CarChecks.class )
.type( RentalCar.class )
    .defaultGroupSequenceProvider( RentalCarGroupSequenceProvider.class ); 

 

Once a ConstraintMapping is set up it has to be passed to the configuration. Since the programmatic API is not part of the official Bean Validation specification you need to get hold of a HibernateValidatorConfiguration instance as shown in 例 8.16 “Creating a Hibernate Validator specific configuration”.

例 8.16. Creating a Hibernate Validator specific configuration

ConstraintMapping mapping = new ConstraintMapping();
// configure mapping instance

HibernateValidatorConfiguration config = Validation.byProvider( HibernateValidator.class ).configure();
config.addMapping( mapping );
ValidatorFactory factory = config.buildValidatorFactory();
Validator validator = factory.getValidator();

 

8.5. Boolean composition for constraint composition

As per Bean Validation specification the constraints of a composed constraint (see 第 3.2 节 “约束条件组合”) are all combined via a logical AND. This means all of the composing constraints need to return true in order for an overall successful validation. Hibernate Validator offers an extension to this logical AND combination which allows you to compose constraints via a logical OR or NOT. To do so you have to use the ConstraintComposition annotation and the enum CompositionType with its values ANDOR and ALL_FALSE例 8.17 “OR composition of constraints” shows how to build a composing constraint where only one of the constraints has to be successful in order to pass the validation. Either the validated string is all lowercased or it is between two and three characters long.

例 8.17. OR composition of constraints

@ConstraintComposition(OR)
@Pattern(regexp = "[a-z]") @Size(min = 2, max = 3) @ReportAsSingleViolation @Target({ METHOD, FIELD }) @Retention(RUNTIME) @Constraint(validatedBy = { }) public @interface PatternOrSize {    public abstract String message() default "{PatternOrSize.message}";    public abstract Class<?>[] groups() default { };    public abstract Class<? extends Payload>[] payload() default { }; }

提示

Using ALL_FALSE as composition type implicitly enforces that only a single violation will get reported in case validation of the constraint composition fails.

第 9 章 Annotation Processor

你碰到过下面这些让人抓狂的状况么:

  • specifying constraint annotations at unsupported data types (e.g. by annotating a String with @Past)

  • 对一个JavaBean的setter方法进行标注(而不是getter)

  • 对一个静态的变量或者方法进行约束条件标注(这样是不支持滴)

这样的话, 你就应该看看Hibernate Validator 的约束处理器了. 它会被插入到编译过程当中, 而后若是发现若是哪一个约束标注用错了的话, 则汇报编译错误.

注意

You can find the Hibernate Validator Annotation Processor as part of the distribution bundle on Sourceforge or in the JBoss Maven Repository (see 例 1.1 “Configuring the JBoss Maven repository”) under the GAV org.hibernate:hibernate-validator-annotation-processor.

9.1. 前提条件

Hibernate Validator的标注处理器是基于JSR 269所定义的"可插入式标注处理API"的. 这个API从Java 6开始已是Java 平台的一部分了, 因此请确保使用这个或者之后的版本.

9.2. 特性

As of Hibernate Validator 4.2.0.Final the Hibernate Validator Annotation Processor checks that:

  • 应用了约束标注的属性的类型是否被该约束所支持

  • only non-static fields or methods are annotated with constraint annotations

  • only non-primitive fields or methods are annotated with @Valid

  • only such methods are annotated with constraint annotations which are valid JavaBeans getter methods (optionally, see below)

  • only such annotation types are annotated with constraint annotations which are constraint annotations themselves

  • definition of dynamic default group sequence with @GroupSequenceProvider is valid

9.3. 配置项

Hibernate Validator标注处理器的行为能够经过表 9.1 “Hibernate Validator 标注处理器配置项”中列出的处理器配置项加以控制.

表 9.1. Hibernate Validator 标注处理器配置项

配置项 功能
diagnosticKind 控制编译错误级别. 必须是枚举类型javax.tools.Diagnostic.Kind中的值(字符串形式), 例如WARNING. 若是是ERROR的话, 那么若是API检测到约束信息应用的错误的话, 会让编译过程终止, 默认是ERROR.
methodConstraintsSupported Controls whether constraints are allowed at methods of any kind. Must be set to true when working with method level constraints as supported by Hibernate Validator. Can be set to false to allow constraints only at JavaBeans getter methods as defined by the Bean Validation API. Defaults to true.
verbose Controls whether detailed processing information shall be displayed or not, useful for debugging purposes. Must be either true or false. Defaults to false.

9.4. 使用标注处理器

本小节详细介绍了如何把Hibernate Validator标注处理器与命令行编译(javac, Ant, Maven)以及IDE (Eclipse, IntelliJ IDEA, NetBeans)集成.

9.4.1. 命令行编译

9.4.1.1. javac

当使用命令行(javac)编译的时候, 经过"processorpath"属性指定下列jar:

  • validation-api-1.0.0.GA.jar

  • hibernate-validator-4.2.0.Final.jar

  • hibernate-validator-annotation-processor-4.2.0.Final.jar

下面显示了一个具体的示例. 这样, 标注处理器就会自动被编译器检测到而且调用.

例 9.1. 在javac中使用标注处理器

javac src/main/java/org/hibernate/validator/ap/demo/Car.java \
   -cp /path/to/validation-api-1.0.0.GA.jar \
   -processorpath /path/to/validation-api-1.0.0.GA.jar:/path/to/hibernate-validator-4.2.0.Final:/path/to/hibernate-validator-annotation-processor-4.2.0.Final.jar

9.4.1.2. Apache Ant

和直接使用javac差很少, 能够在Apache Antjavac task中添加上面例子中的参数:

例 9.2. 在Ant中使用标注处理器

<javac srcdir="src/main"
       destdir="build/classes"
       classpath="/path/to/validation-api-1.0.0.GA.jar">
       <compilerarg value="-processorpath" />
       <compilerarg value="/path/to/validation-api-1.0.0.GA.jar:/path/to/hibernate-validator-4.2.0.Final:/path/to/hibernate-validator-annotation-processor-4.2.0.Final.jar"/>
</javac>

9.4.1.3. Maven

对于和Apache Maven集成来讲咱们有不少选择, 一般, 咱们能够把Hibenrate Validator标注处理器做为依赖添加到你的项目当中:

例 9.3. 添加HV 标注处理器为依赖

...
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>4.2.0.Final</version>
    <scope>compile</scope>
</dependency>
...        

这样, 这个处理器就可以自动的被编译器所调用. 虽然基本上能工做,可是仍是有一些缺点, 在某些状况下, 标注处理器的输出信息可能不可以被显示出来. (请参考MCOMPILER-66).

另外的一个选择是使用Maven Annotation Plugin. 不过在此文档撰写的时候, 这个插件尚未被上传到任何一个普遍被使用的仓库中. 因此, 你须要本身把这个插件本身的仓库添加到你的settings.xml 或 pom.xml中:

例 9.4. 添加Maven Annotation Plugin的仓库

...
<pluginRepositories>
    <pluginRepository>
        <id>maven-annotation-plugin-repo</id>
        <url>http://maven-annotation-plugin.googlecode.com/svn/trunk/mavenrepo</url>
    </pluginRepository>
</pluginRepositories>
...                      


如今, 禁用compiler插件所调用的标准的标注处理过程, 而后再经过定义一个execution来配置annotation plugin的运行, 还须要把Hibernate Validator标注处理器做为该插件的依赖添加进去(这样, 此标注处理器就不会被当成你的项目的依赖而出如今类路径中了):

例 9.5. 配置Maven Annotation Plugin

...
<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <source>1.6</source>
        <target>1.6</target>
        <compilerArgument>-proc:none</compilerArgument>
    </configuration>
</plugin>
<plugin>
    <groupId>org.bsc.maven</groupId>
    <artifactId>maven-processor-plugin</artifactId>
    <version>1.3.4</version>
    <executions>
        <execution>
            <id>process</id>
            <goals>
                <goal>process</goal>
            </goals>
            <phase>process-sources</phase>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator-annotation-processor</artifactId>
            <version>4.2.0.Final</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</plugin>
...
                    

9.4.2. IDE集成

9.4.2.1. Eclipse

请参考如下步骤来在Eclipse中使用标注处理器:

  • 右键点击你的项目, 而后选择"属性"

  • 在"Java Compiler"页面确认"编译级别"设置的是"1.6". 不然的话是没法使用标注处理器的.

  • 到"Java Compiler"下面的"Annotation Processing" 页面, 而后选择"启用标注处理"(译注: 个人电脑是英文版的, 因此真的不知道中文版的eclipse上, 这些翻译成了什么:(

  • 到"Java Compiler - Annotation Processing - Factory Path"页面, 而后添加下面的jar文件:

    • validation-api-1.0.0.GA.jar

    • hibernate-validator-4.2.0.Final.jar

    • hibernate-validator-annotation-processor-4.2.0.Final.jar

  • 确认工做空间从新编译

如今你应该可以看到全部的标注错误都在编辑窗口中显示出了错误标记,也都显示在了"问题"视图:

9.4.2.2. IntelliJ IDEA

请参考如下步骤来在IntelliJ IDEA (9.0及以上):中使用标注处理器:

  • 选择 "File", 而后 "Settings",

  • 展开"Compiler"节点, 而后点击"Annotation Processors"

  • Choose "Enable annotation processing" and enter the following as "Processor path": /path/to/validation-api-1.0.0.GA.jar:/path/to/hibernate-validator-4.2.0.Final.jar:/path/to/hibernate-validator-annotation-processor-4.2.0.Final.jar

  • 添加处理器的全路径名称org.hibernate.validator.ap.ConstraintValidationProcessor到"Annotation Processors"列表

  • 若是须要的话, 添加你的模块到"Processed Modules"列表

从新编译你的项目, 而后应该能看到关于约束标注的错误信息了:

9.4.2.3. NetBeans

从6.9这个版本开始, NetBeans也支持标注处理了. 能够经过下面的步骤来启用它:

  • 右键点击你的项目, 而后选择"属性"

  • Go to "Libraries", tab "Processor", and add the following JARs:

    • validation-api-1.0.0.GA.jar

    • hibernate-validator-4.2.0.Final.jar

    • hibernate-validator-annotation-processor-4.2.0.Final.jar

  • 到"Build - Compiling"页面选中"Enable Annotation Processing" 和 "Enable Annotation Processing in Editor", 而且指定标注处理器的全路径名称org.hibernate.validator.ap.ConstraintValidationProcessor.

全部的约束标注问题应该都会在编辑器里面直接被标记出来了:

9.5. 已知问题

如下是截止到2010年五月咱们发现(但还没有解决)的问题:

  • HV-308: Additional validators registered for a constraint using XML are not evaluated by the annotation processor.

  • 有时候, 在eclipse里面自定义的约束条件不可以被正确的检查. 清理这个项目可能会有帮助. 这多是由于Eclipse中对 JSR 269 API的实现有问题, 可是还须要进一步的研究.

  • When using the processor within Eclipse, the check of dynamic default group sequence definitions doesn't work. After further investigation, it seems to be an issue with the Eclipse JSR 269 API implementation.

第 10 章 进一步阅读

Last but not least, a few pointers to further information.

A great source for examples is the Bean Validation TCK which is available for anonymous access on GitHub. In particular the TCK's tests might be of interest. The JSR 303 specification itself is also a great way to deepen your understanding of Bean Validation resp. Hibernate Validator.

若是你还有什么关于Hibernate Validator的问题或者想和咱们分享你的使用经验,请使用Hibernate Validator Wiki或者去Hibernate Validator Forum发帖子(译注:可是不要灌水:-D).

若是你发现了Hibernate Validator的bug,请在Hibernate's Jira中报告, 咱们很是欢迎您的反馈!

相关文章
相关标签/搜索