Hibernate.orgCommunity Documentation

Hibernate Validator

JSR 303 的参考实现

使用指南

4.3.2.Final

July 25, 2014


序言
1. 开始入门
1.1. Project set up
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. 进一步阅读

本章将会告诉你如何使用Hibernate Validator, 在开始之前,你需要准备好下面的环境:

In order to use Hibernate Validator within an existing Maven project, simply add the following dependency to your pom.xml:


Alternatively, you can start by creating a sample project using Hibernate Validator's Quickstart Maven archetype as follows:


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

mvn test

Maven will compile the example code and run the implemented unit tests. Let's have a look at the actual code in the next section.

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

例 1.5. 在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中的类, 并没有直接调用参考实现中的任何类,所以, 没有任何问题如果切换到其他的实现.

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

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

如果你的模型遵循JavaBeans规范的话, 你还可以把约束标注在属性上. 例 2.2 “属性级约束”例 2.1 “字段级(field level) 约束”的唯一不同就是它的约束是定义在属性级别上的.

注意

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


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.

Last but not least, a constraint can also be placed on class level. When a constraint annotation is placed on this level the class instance itself passed to the ConstraintValidator. Class level constraints are useful if it is necessary to inspect more than a single property of the class to validate it or if a correlation between different state variables has to be evaluated. In 例 2.3 “类级别约束” we add the property passengers to the class Car. We also add the constraint PassengerCount on the class level. We will later see how we can actually create this custom constraint (see 第 3 章 创建自己的约束规则). For now it is enough to know that PassengerCount will ensure that there cannot be more passengers in a car than there are seats.


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



如果校验Car的实例对象的话,因为它的driver属性标注了@Valid, 那么关联的Person也会被校验. 所以,如果对象Personname属性如果是null的话,那么校验会失败.

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

  • 数组

  • 实现了java.lang.Iterable接口( 例如Collection, ListSet)

  • 实现了java.util.Map接口

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


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

注意

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

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

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

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

All the validation methods have a var-args parameter which can be used to specify, which validation groups shall be considered when performing the validation. If the parameter is not specified the default validation group (javax.validation.groups.Default) will be used. We will go into more detail on the topic of validation groups in 第 2.3 节 “校验组”

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

因为大括号{ 在这里是特殊字符,所以,你可以通过使用反斜线来对其进行转义, 例如:

  • \{ 被转义成 {

  • \} 被转义成 }

  • \\ 被转义成 \

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

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

注意

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


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



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


现在, 在我们的例子中有三个不同的校验组, Person.name, Car.manufacturer, Car.licensePlateCar.seatCount都属于默认(Default) 组, Driver.ageDriver.hasDrivingLicense 从属于 DriverChecks组, 而Car.passedVehicleInspectionCarChecks组中. 例 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组的校验就通过了.

最后, 让我们再来校验所有的组中定义的约束条件,可以看到所有的约束条件都通过了验证.

By default, constraints are evaluated in no particular order, regardless of which groups they belong to. In some situations, however, it is useful to control the order constraints are evaluated. 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.

注意

If at least one constraint fails in a sequenced group none of the constraints of the following groups in the sequence get validated.


警告

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

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


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 you can evaluate the constraints belonging to RentalChecks, CarChecks and RentalCar by just requesting the Default group as seen in 例 2.20 “RentalCar with redefined default group”.



注意

Due to the fact that there cannot be a cyclic dependency in the group and group sequence definitions one cannot just add Default to the sequence redefining Default for a class. Instead the class itself has to be added!

注意

The Default group sequence overriding is local to the class it is defined on and is not propagated to the associated objects. This means in particular that adding DriverChecks to the default group sequence of RentalCar would not have any effects. Only the group Default will be propagated to the driver association when validation a rental car instance.

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 CarChecks as seen in 例 2.21 “RentalCar with @GroupSequenceProvider” and 例  “DefaultGroupSequenceProvider implementation”.



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” and 表 2.4 “Custom country specific 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

AnnotationSupported data types作用Hibernate metadata impact
@AssertFalseBoolean, booleanChecks that the annotated element is false.没有
@AssertTrueBoolean, booleanChecks that the annotated element is true.没有
@DecimalMaxBigDecimal, BigInteger, String, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number and CharSequence.被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.没有
@DecimalMinBigDecimal, BigInteger, String, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number and CharSequence.被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.没有
@Digits(integer=, fraction=)BigDecimal, BigInteger, String, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number and CharSequence.Checks whether the annoted value is a number having up to integer digits and fraction fractional digits.对应的数据库表字段会被设置精度(precision)和准度(scale).
@Futurejava.util.Date, java.util.Calendar; Additionally supported by HV, if the Joda Time date/time API is on the class path: any implementations of ReadablePartial and ReadableInstant.检查给定的日期是否比现在晚.没有
@MaxBigDecimal, BigInteger, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type of Number.检查该值是否小于或等于约束条件中指定的最大值.会给对应的数据库表字段添加一个check的约束条件.
@MinBigDecimal, BigInteger, byte, short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of CharSequence (the numeric value represented by the char sequence is evaluated), any sub-type of Number.检查该值是否大于或等于约束条件中规定的最小值.会给对应的数据库表字段添加一个check的约束条件.
@NotNullAny typeChecks that the annotated value is not null.对应的表字段不允许为null.
@NullAny typeChecks that the annotated value is null.没有
@Pastjava.util.Date, java.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. Additionally supported by HV: any sub-type of CharSequence.检查该字符串是否能够在match指定的情况下被regex定义的正则表达式匹配.没有
@Size(min=, max=)String, Collection, Map and arrays. Additionally supported by HV: any sub-type of CharSequence.Checks if the annotated element's size is between min and max (inclusive).对应的数据库表字段的长度会被设置成约束中定义的最大值.
@ValidAny non-primitive type递归的对关联对象进行校验, 如果关联对象是个集合或者数组, 那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.没有

注意

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

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”. With one exception also these constraints apply to the field/property level, only @ScriptAssert is a class-level constraint.

表 2.3. Custom constraints

AnnotationSupported data types作用Hibernate metadata impact
@CreditCardNumberCharSequenceChecks that the annotated character sequence 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.没有
@EmailCharSequenceChecks whether the specified character sequence is a valid email address. The optional parameters regexp and flags allow to specify an additional regular expression (including regular expression flags) which the email must match.没有
@Length(min=, max=)CharSequenceValidates that the annotated character sequence is between min and max included.对应的数据库表字段的长度会被设置成约束中定义的最大值.
@ModCheck(modType=, multiplier=, startIndex=, endIndex=, checkDigitPosition=, ignoreNonDigitCharacters=)CharSequenceChecks that the digits within the annotated character sequence pass the mod 10 or mod 11 checksum algorithm. modType is used to select the modulo type and the multiplier determines the algorithm specific multiplier (see also Luhn algorithm). startIndex and endIndex allow to only run the modulo algorithm on the specified sub-string. checkDigitPosition allows to use an arbitrary digit within the character sequence to be the check digit. If not specified it is assumed that the check digit is part of the specified range. Last but not least, ignoreNonDigitCharacters allows to ignore non digit characters.没有
@NotBlankCharSequenceChecks that the annotated character sequence 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.没有
@NotEmptyCharSequence, Collection, Map and arraysChecks whether the annotated element is not null nor empty.没有
@Range(min=, max=)BigDecimal, BigInteger, CharSequence, byte, short, int, long and the respective wrappers of the primitive typesChecks whether the annotated value lies between (inclusive) the specified minimum and maximum.没有
@SafeHtml(whitelistType=, additionalTags=)CharSequenceChecks 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 typeChecks whether the given script can successfully be evaluated against the annotated element. In order to use this constraint, an implementation of the Java Scripting API as defined by JSR 223 ("Scripting for the JavaTM Platform") must part of the class path. The expressions to be evaluated can be written in any scripting or expression language, for which a JSR 223 compatible engine can be found in the class path.没有
@URL(protocol=, host=, port=, regexp=, flags=)CharSequenceChecks if the annotated character sequence is a valid URL according to RFC2396. If any of the optional parameters protocol, host 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.没有

尽管Bean Validation API定义了一大堆标准的约束条件, 但是肯定还是有这些约束不能满足我们需求的时候, 在这种情况下, 你可以根据你的特定的校验需求来创建自己的约束条件.

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

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

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


现在我们可以来定义真正的约束标注了. 如果你以前没有创建过标注(annotation)的话,那么这个可能看起来有点吓人, 可是其实没有那么难的 :)


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

除了这三个强制性要求的属性(message, groupspayload) 之外, 我们还添加了一个属性用来指定所要求到字符串模式. 此属性的名称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.

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:


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

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

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

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

例 3.3 “约束条件CheckCase的验证器” 中的isValid使用了约束条件中定义的错误消息模板, 然后返回一个true 或者 false. 通过使用传入的ConstraintValidatorContext对象, 我们还可以给约束条件中定义的错误信息模板来添加额外的信息或者完全创建一个新的错误信息模板.


例 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”.


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


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


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

这个问题可以通过使用组合约束条件来解决. 接下来让我们来创建一个新的约束标注@ValidLicensePlate, 它组合了@NotNull, @Size@CheckCase:


我们只需要把要组合的约束标注在这个新的类型上加以声明 (注: 这正是我们为什么把annotation types作为了@CheckCase的一个target). 因为这个组合不需要额外的校验器, 所以不需要声明validator属性.

现在, 在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:


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


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


警告

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

validation.xml中所有的配置信息都是可选的, 例 4.2 “validation.xml”中就是列出了Hibernate Validator中的默认值. 如果类路径当中存在有多个Bean Validation的实现的话, 那么可以通过default-provider节点指定使用那个Bean Validation的实现. message-interpolator, traversable-resolverconstraint-validator-factory可以用来指定自定义的javax.validation.MessageInterpolator, javax.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.

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


例 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">
        <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上添加的约束标注信息, 这个属性的默认值就是true. ignore-annotations 属性还可以定义在class, fieldsgetter属性上, 如果没有明确指定的话, 那么默认级别是bean (可参考 第 2.1 节 “定义约束”). constraint 节点用于添加一个约束条件到其父节点对应的元素上, 并且它需要通过annotation属性来指定需要使用哪个约束条件. 对于每个约束条件中所需要的属性, 其中, 由Bean Validation 规范规定的属性(message, groupspayload) 可以通过同名的子节点来定义, 而每个约束条件中自定义的属性, 则需要使用element节点来定义.

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

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

第 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. Generally, an available provider is discovered by the Java Service Provider mechanism. A Bean Validation implementation includes the file javax.validation.spi.ValidationProvider in META-INF/services. This file contains the fully qualified classname of the ValidationProvider of the implementation. In the case of Hibernate Validator this is org.hibernate.validator.HibernateValidator.

注意

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

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


你也可以通过Validation.byDefaultProvider()现获取一个Configuration对象, 这样可以对要创建的Validator进行配置.


MessageInterpolator, TraversableResolverConstraintValidatorFactory会在后面详细介绍.

最后, 你可以指定使用哪个Bean Validation的实现. 如果类路径下存在多个Bean Validation的实现的话,这样就很有必要了. 例如, 如果你想使用Hibernate Validator来作为内部实现来创建Validator的话:


提示

创建出来的Validator实例是线程安全的, 所以你可以把它缓存起来.

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


提示

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().

到目前位置我们还没有讨论过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”.


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


你需要实现此接口:


警告

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

注意

ConstraintValidatorFactory不应该缓存其创建的实例, 因为每个实例都可能在其的初始化方法中被修改.

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.

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”).

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.

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

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的约束标注”).

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

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.

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.jboss.logging.*, [3.1.0,4.0.0)

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

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

Hibernate Validator不仅能够和Hibernate集成工作, 还能够和任何JPA的实现很好的一起工作.

Hibernate Annotations (即 Hibernate 3.5.x) 中包含了一个的Hibernate 事件监听器(译注: 请阅读Hibernate Core文档了解Hibernate的事件模型) - org.hibernate.cfg.beanvalidation.BeanValidationEventListener - 来为Hibernate Validator服务. 当一个PreInsertEvent, PreUpdateEventPreDeleteEvent事件发生的时候, 这个监听器就可以对该事件所涉及到的实体对象进行校验, 如果校验不通过的话, 则抛出异常. 默认情况下, Hibernate在对每个对象进行保存或者修改操作的时候,都会对其进行校验, 而删除操作则不会. 你可以通过javax.persistence.validation.group.pre-persist, javax.persistence.validation.group.pre-updatejavax.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中定义如下的配置信息:


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.xml. persistence.xml also defines a node validation-mode which can be set to AUTO, CALLBACK, NONE. The default is AUTO.

对于JPA1来讲, 你需要自己创建和注册Hibernate Validator. 如果你是使用Hibernate EntityManager, 那么你可以把第 7.3.1 节 “基于Hibernate事件模型的校验”中列出来的BeanValidationEventListener类添加到你的项目中, 然后再手工注册它.

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


提示

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}

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.

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

PackagesDescription
org.hibernate.validatorThis 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.defsWith 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, org.hibernate.validator.constraints.brIn addition to Bean Validation constraints, Hibernate Validator provides some useful custom constraints. These packages contain all custom annotation classes. For more details see 第 2.4.2 节 “Additional constraints”.
org.hibernate.validator.group, org.hibernate.validator.spi.groupWith Hibernate Validator you can define dynamic default group sequences in function of the validated object state. These packages contain 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, org.hibernate.validator.spi.resourceloadingThese 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.metadataHibernate 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.

注意

The public packages of Hibernate Validator fall into two categories: while the actual API parts are intended to be invoked or used by clients (e.g. the API for programmatic constraint declaration or the custom constraints), the SPI (service provider interface) packages contain interfaces which are intended to be implemented by clients (e.g. ResourceBundleLocator).

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.




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

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

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


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

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


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.

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.


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.

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.


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


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.


注意

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.

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.


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

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


As you can see constraints can be configured on multiple classes and properties using method chaining. The constraint definition classes NotNullDef, SizeDef 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()”.


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.


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


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


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


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 AND, OR 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.


提示

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

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

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

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

When compiling on the command line using javac, specify the JAR hibernate-validator-annotation-processor-4.3.2.Final.jar using the "processorpath" option as shown in the following listing. The processor will be detected automatically by the compiler and invoked during compilation.


对于和Apache Maven集成来说我们有很多选择, 通常, 我们可以把Hibenrate Validator标注处理器作为依赖添加到你的项目当中:


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

另外的一个选择是使用Maven Annotation Plugin. 不过在此文档撰写的时候, 这个插件还没有被上传到任何一个广泛被使用的仓库中. 所以, 你需要自己把这个插件自己的仓库添加到你的settings.xml 或 pom.xml中:


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


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

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

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

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

  • Go to "Java Compiler - Annotation Processing - Factory Path" and add the JAR hibernate-validator-annotation-processor-4.3.2.Final.jar

  • 确认工作空间重新编译

现在你应该能够看到所有的标注错误都在编辑窗口中显示出了错误标记,也都显示在了"问题"视图:

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

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

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

  • Choose "Enable annotation processing" and enter the following as "Processor path": /path/to/hibernate-validator-annotation-processor-4.3.2.Final.jar

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

  • 如果需要的话, 添加你的模块到"Processed Modules"列表

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

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

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

  • Go to "Libraries", tab "Processor", and add the JAR hibernate-validator-annotation-processor-4.3.2.Final.jar

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

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

以下是截止到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.

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中报告, 我们非常欢迎您的反馈!