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.
本章将会告诉你如何使用Hibernate Validator, 在开始之前,你需要准备好下面的环境:
A JDK >= 5
网络连接 ( Maven需要通过互联网下载所需的类库)
-
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 aboutsettings.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.
使用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
例 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
我们需要使用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中的类, 并没有直接调用参考实现中的任何类,所以, 没有任何问题如果切换到其他的实现.
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 章 创建自己的约束规则.
在本章中,我们会详细的介绍如何使用Hibernate Validator 来对一个给定的实体模型进行验证. 还会介绍Bean Validation规范提供了哪些默认的约束条件和Hibernate Validator提供了哪些额外的. 让我们先从如何给一个实体添加约束开始.
注意
不是所有的约束都能够被用在所有的类结构上. 事实上, 没有任何定义在Bean Validation规范中的约束可以被用在class上. 约束定义中的java.lang.annotation.Target
属性定义了这个约束能够被使用在哪个层次结构上. 详细信息请参考第 3 章 创建自己的约束规则.
约束条件能够被标注在类的字段上面, 请参考示例例 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) 对此没有影响.
注意
静态字段或者属性是不会被校验的.
如果你的模型遵循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.
最后, 一个约束也能够被放在类级别上. 当一个约束被标注在一个类上的时候,这个类的实例对象被传递给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.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
重写了父类Car
的getManufacturer()
方法, 那么定义在父类的这个方法上的约束和子类这个方法上定义的约束都会被校验.
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
也会被校验. 所以,如果对象Person
的name属性如果是null
的话,那么校验会失败.
关联校验也适用于集合类型的字段, 也就是说,任何下列的类型:
数组
实现了
java.lang.Iterable
接口( 例如Collection
,List
和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
值的.
Validator
是Bean Validation中最主要的接口, 我们会在第 5.1 节 “Configuration 和 ValidatorFactory”中详细介绍如何获取一个Validator
的实例, 现在先让我们来看看如何使用Validator
接口中的各个方法.
例 2.8. Validation.buildDefaultValidatorFactory()
ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator();
第 5 章 Bootstrapping介绍了其他的获取Validator实例的方法. 现在我们的目标是学习如何使用Validator
来校验实体对象.
Validator
中有三个方法能够被用来校验整个实体对象或者实体对象中的属性.
所有的校验方法都接收零个或多个用来定义此次校验是基于哪个校验组的参数. 如果没有给出这个参数的话, 那么此次校验将会基于默认的校验组 (javax.validation.groups.Default
). 第 2.3 节 “校验组”
使用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());
通过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 节 “展示层校验”).
通过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
.
现在是时候看看究竟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() |
导致校验失败的约束定义. |
在第 3 章 创建自己的约束规则中,我们会看到,每个约束定义中都包含有一个用于提示验证结果的消息模版, 并且在声明一个约束条件的时候,你可以通过这个约束中的message
属性来重写默认的消息模版, 可以参考例 2.13 “Driver”. 如果在校验的时候,这个约束条件没有通过,那么你配置的MessageInterpolator
会被用来当成解析器来解析这个约束中定义的消息模版, 从而得到最终的验证失败提示信息. 这个解析器会尝试解析模版中的占位符( 大括号括起来的字符串 ). 其中, Hibernate Validator中默认的解析器 (MessageInterpolator
) 会先在类路径下找名称为ValidationMessages.properties
的ResourceBundle
, 然后将占位符和这个文件中定义的resource进行匹配,如果匹配不成功的话,那么它会继续匹配Hibernate Validator自带的位于/org/hibernate/validator/ValidationMessages.properties
的ResourceBundle
, 依次类推,递归的匹配所有的占位符.
因为大括号{ 在这里是特殊字符,所以,你可以通过使用反斜线来对其进行转义, 例如:
\{ 被转义成 {
\} 被转义成 }
\\ 被转义成 \
如果默认的消息解析器不能够满足你的需求,那么你也可以在创建ValidatorFactory
的时候, 将其替换为一个你自定义的MessageInterpolator
, 具体请参考 第 5 章 Bootstrapping.
校验组能够让你在验证的时候选择应用哪些约束条件. 这样在某些情况下( 例如向导 ) 就可以对每一步进行校验的时候, 选取对应这步的那些约束条件进行验证了. 校验组是通过可变参数传递给validate
, validateProperty
和 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; } }
最后, 我们给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
组的校验就通过了.
最后, 让我们再来校验所有的组中定义的约束条件,可以看到所有的约束条件都通过了验证.
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() ); }
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
, 而是需要把被重定义的类加入到其中.
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; } }
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.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 |
Boolean , boolean
|
Checks that the annotated element is false . |
没有 |
@AssertTrue |
Boolean , boolean
|
Checks that the annotated element is true . |
没有 |
@DecimalMax |
BigDecimal , BigInteger , String , byte , short , int , long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number . |
被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal 定义的最大值的字符串表示. |
没有 |
@DecimalMin |
BigDecimal , BigInteger , String , byte , short , int , long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number . |
被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过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 . |
Checks whether the annoted value is a number having up to integer digits and fraction fractional digits. |
对应的数据库表字段会被设置精度(precision)和准度(scale). |
@Future |
java.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 . |
检查给定的日期是否比现在晚. | 没有 |
@Max |
BigDecimal , BigInteger , byte , short , int , long 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 |
BigDecimal , BigInteger , byte , short , int , long 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.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 |
检查该字符串是否能够在match 指定的情况下被regex 定义的正则表达式匹配. |
没有 |
@Size(min=, max=) |
String , Collection , Map 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 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 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. | 没有 |
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 |
String , Collection , Map and arrays |
Checks whether the annotated element is not null nor empty. |
没有 |
@Range(min=, max=) |
BigDecimal , BigInteger , String , byte , short , int , long 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 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. |
没有 |
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 章 创建自己的约束规则.
尽管Bean Validation API定义了一大堆标准的约束条件, 但是肯定还是有这些约束不能满足我们需求的时候, 在这种情况下, 你可以根据你的特定的校验需求来创建自己的约束条件.
让我们来创建一个新的用来判断一个给定字符串是否全是大写或者小写字符的约束标注. 我们将稍后把它用在第 1 章 开始入门中的类Car
的licensePlate字段上来确保这个字段的内容一直都是大写字母.
首先,我们需要一种方法来表示这两种模式( 译注: 大写或小写), 我们可以使用String
常量, 但是在Java 5中, 枚举类型是个更好的选择:
现在我们可以来定义真正的约束标注了. 如果你以前没有创建过标注(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.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.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; }
最后, 我们还需要指定如果@CheckCase
这个约束条件验证的时候,没有通过的话的校验错误信息. 我们可以添加下面的内容到我们项目自定义的ValidationMessages.properties
(参考 第 2.2.4 节 “验证失败提示信息解析”)文件中.
如果发现校验错误了的话, 你所使用的Bean Validation的实现会用我们定义在@CheckCase
中message属性上的值作为键到这个文件中去查找对应的错误信息.
现在我们已经有了一个自定义的约束条件了, 我们可以把它用在第 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.7 “应用CheckCase约束条件”中我们可以看到, 类Car
的licensePlate属性上定义了三个约束条件. 在某些复杂的场景中, 可能还会有更多的约束条件被定义到同一个元素上面, 这可能会让代码看起来有些复杂, 另外, 如果在另外的类里面还有一个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 {}; }
我们可以使用validation.xml
来对Hibernate Validator进行配置. ValidationFactory
在初始化的时候会在类路径下寻找此文件,如果找到的话,就会应用其中定义的配置信息. 例 4.1 “validation-configuration-1.0.xsd”显示了valiation.xml
的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.MessageInterpolator
, javax.validation.TraversableResolver
和javax.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.xml
的constraint-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" 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上添加的约束标注信息, 这个属性的默认值就是true
. ignore-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.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()
.
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();
MessageInterpolator
, TraversableResolver
和 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
实例是线程安全的, 所以你可以把它缓存起来.
如果 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(); }
第 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 ofString.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()
.
一个普遍的需求是你可能需要为错误消息解析指定你自己的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 文档.
到目前位置我们还没有讨论过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 TraversableResolver
s 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();
最后, 还有个配置项得提一下, 那就是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. */ <T extends ConstraintValidator<?,?>> T getInstance(Class<T> key); }
警告
如果一个约束条件的实现需要依赖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 ConstraintDescriptor
s 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 PropertyDescriptor
s (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
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.
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 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
如果因为某种原因, 你不想使用这个特性, 那么可以将hibernate.validator.apply_to_ddl
属性设置为false
. 请参考???.
你也可以限制这个DDL约束自动生成的特性只应用到一部分实体类. 只需要设置org.hibernate.validator.group.ddl属性, 这个属性的值是你想要应用此特性的实体类的全路径名称, 每个以逗号分隔.
Hibernate Validator不仅能够和Hibernate集成工作, 还能够和任何JPA的实现很好的一起工作.
Hibernate Annotations (即 Hibernate 3.5.x) 中包含了一个的Hibernate 事件监听器(译注: 请阅读Hibernate Core文档了解Hibernate的事件模型) - org.hibernate.cfg.beanvalidation.BeanValidationEventListener
- 来为Hibernate Validator服务. 当一个PreInsertEvent
, PreUpdateEvent
或 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>
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规范.
例 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}
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
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 . |
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 ...
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.
例 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:
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.
Special care must be taken when defining parameter constraints in inheritance hierarchies.
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.
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.
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.
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() );
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();
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.
例 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.
你碰到过下面这些让人抓狂的情况么:
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的标注处理器是基于JSR 269所定义的"可插入式标注处理API"的. 这个API从Java 6开始已经是Java 平台的一部分了, 所以请确保使用这个或者以后的版本.
As of Hibernate Validator 4.2.0.Final the Hibernate Validator Annotation Processor checks that:
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 . |
当使用命令行(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
和直接使用javac差不多, 可以在Apache Ant的javac 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>
对于和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> ...
例 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> ...
右键点击你的项目, 然后选择"属性"
在"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
确认工作空间重新编译
现在你应该能够看到所有的标注错误都在编辑窗口中显示出了错误标记,也都显示在了"问题"视图:
请参考以下步骤来在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"列表
重新编译你的项目, 然后应该能看到关于约束标注的错误信息了:
从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
.
所有的约束标注问题应该都会在编辑器里面直接被标记出来了:
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中报告, 我们非常欢迎您的反馈!