Hibernate Validator

Hibernate Validator

JSR 303 的參考實現

使用指南

由  Hardy Ferentschik和Gunnar Morling
and thanks to  Shaozhuang Liu

4.2.0.Finalphp

June 20, 2011java


序言
1. 開始入門
1.1. 第一個Maven項目
1.2. 添加約束
1.3. 校驗約束
1.4. 更進一步
2. Validation step by step
2.1. 定義約束
2.1.1. 字段級(field level) 約束
2.1.2. 屬性級別約束
2.1.3. 類級別約束
2.1.4. 約束繼承
2.1.5. 對象圖
2.2. 校驗約束
2.2.1. 獲取一個Validator的實例
2.2.2. Validator中的方法
2.2.3. ConstraintViolation 中的方法
2.2.4. 驗證失敗提示信息解析
2.3. 校驗組
2.3.1. 校驗組序列
2.3.2. 對一個類重定義其默認校驗組
2.4. 內置的約束條件
2.4.1. Bean Validation constraints
2.4.2. Additional constraints
3. 建立本身的約束規則
3.1. 建立一個簡單的約束條件
3.1.1. 約束標註
3.1.2. 約束校驗器
3.1.3. 校驗錯誤信息
3.1.4. 應用約束條件
3.2. 約束條件組合
4. XML configuration
4.1. validation.xml
4.2. 映射約束
5. Bootstrapping
5.1. Configuration 和 ValidatorFactory
5.2. ValidationProviderResolver
5.3. MessageInterpolator
5.3.1. ResourceBundleLocator
5.4. TraversableResolver
5.5. ConstraintValidatorFactory
6. Metadata API
6.1. BeanDescriptor
6.2. PropertyDescriptor
6.3. ElementDescriptor
6.4. ConstraintDescriptor
7. 與其餘框架集成
7.1. OSGi
7.2. 與數據庫集成校驗
7.3. ORM集成
7.3.1. 基於Hibernate事件模型的校驗
7.3.2. JPA
7.4. 展現層校驗
8. Hibernate Validator Specifics
8.1. Public API
8.2. Fail fast mode
8.3. Method validation
8.3.1. Defining method-level constraints
8.3.2. Evaluating method-level constraints
8.3.3. Retrieving method-level constraint meta data
8.4. Programmatic constraint definition
8.5. Boolean composition for constraint composition
9. Annotation Processor
9.1. 前提條件
9.2. 特性
9.3. 配置項
9.4. 使用標註處理器
9.4.1. 命令行編譯
9.4.2. IDE集成
9.5. 已知問題
10. 進一步閱讀

序言

數據校驗是任何一個應用程序都會用到的功能,不管是顯示層仍是持久層. 一般,相同的校驗邏輯會分散在各個層中, 這樣,不只浪費了時間還會致使錯誤的發生(譯註: 重複代碼). 爲了不重複, 開發人員常常會把這些校驗邏輯直接寫在領域模型裏面, 可是這樣又把領域模型代碼和校驗代碼混雜在了一塊兒, 而這些校驗邏輯更應該是描述領域模型的元數據.node

JSR 303 - Bean Validation - 爲實體驗證定義了元數據模型和API. 默認的元數據模型是經過Annotations來描述的,可是也可使用XML來重載或者擴展. Bean Validation API 並不侷限於應用程序的某一層或者哪一種編程模型, 例如,如圖所示, Bean Validation 能夠被用在任何一層, 或者是像相似Swing的富客戶端程序中.git

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

第 1 章 開始入門

本章將會告訴你如何使用Hibernate Validator, 在開始以前,你須要準備好下面的環境:正則表達式

  • A JDK >= 5數據庫

  • Apache Mavenexpress

  • 網絡鏈接 ( Maven須要經過互聯網下載所需的類庫)apache

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

    例 1.1. Configuring the JBoss Maven repository

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


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

注意

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

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

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

1.1. 第一個Maven項目

使用Maven archetype插件來建立一個新的Maven 項目

例 1.2. 使用Maven archetype 插件來建立一個簡單的基於Hibernate Validator的項目

mvn archetype:generate -DarchetypeGroupId=org.hibernate \
                       -DarchetypeArtifactId=hibernate-validator-quickstart-archetype \
                       -DarchetypeVersion=4.2.0.Final \
                       -DarchetypeRepository=http://repository.jboss.org/nexus/content/groups/public-jboss/ \
                       -DgroupId=com.mycompany \
                       -DartifactId=hv-quickstart

 

Maven 將會把你的項目建立在hv-quickstart目錄中. 進入這個目錄而且執行:

mvn test

這樣, Maven會編譯示例代碼而且運行單元測試, 接下來,讓咱們看看生成的代碼.

注意

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

1.2. 添加約束

在你喜歡的IDE中打開這個項目中的Car類:

例 1.3. 帶約束性標註(annotated with constraints)的Car 類

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

@NotNull@Size and @Min就是上面所屬的約束性標註( constraint annotations), 咱們就是使用它們來聲明約束, 例如在Car的字段中咱們能夠看到:

  • manufacturer永遠不能爲null

  • licensePlate永遠不能爲null,而且它的值字符串的長度要在2到14之間

  • seatCount的值要不能小於2

1.3. 校驗約束

咱們須要使用Validator來對上面的那些約束進行校驗. 讓咱們來看看CarTest這個類:

例 1.4. 在CarTest中使用校驗

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

setUp()方法中,咱們經過ValidatorFactory獲得了一個Validator的實例. Validator是線程安全的,而且能夠重複使用, 因此咱們把它保存成一個類變量. 如今咱們能夠在test方法中使用這個validator的實例來校驗不一樣的car實例了.

validate()方法會返回一個set的ConstraintViolation的實例的集合, 咱們能夠經過遍歷它來查看有哪些驗證錯誤. 前面三個測試用例顯示了一些預期的校驗約束:

  • manufacturerIsNull()中能夠看到manufacturer違反了@NotNull約束

  • licensePlateTooShort()中的licensePlate違反了@Size約束

  • seatCountTooLow()中則致使seatCount違反了@Min約束

若是一個對象沒有校驗出問題的話,那麼validate() 會返回一個空的set對象.

注意,咱們只使用了Bean Validation API中的package javax.validation中的類, 並無直接調用參考實現中的任何類,因此, 沒有任何問題若是切換到其餘的實現.

1.4. 更進一步

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

第 2 章 Validation step by step

在本章中,咱們會詳細的介紹如何使用Hibernate Validator 來對一個給定的實體模型進行驗證. 還會介紹Bean Validation規範提供了哪些默認的約束條件和Hibernate Validator提供了哪些額外的. 讓咱們先從如何給一個實體添加約束開始.

2.1. 定義約束

Bean Validation 的約束是經過Java 註解(annotations)來標註的. 在本節中,咱們會介紹如何使用這些註解(annotations)來標註一個實體模型. 而且,咱們會區分三種不通的註解(annotations) 類型.

注意

不是全部的約束都可以被用在全部的類結構上. 事實上, 沒有任何定義在Bean Validation規範中的約束能夠被用在class上. 約束定義中的java.lang.annotation.Target屬性定義了這個約束可以被使用在哪一個層次結構上. 詳細信息請參考第 3 章 建立本身的約束規則.

2.1.1. 字段級(field level) 約束

約束條件可以被標註在類的字段上面, 請參考示例例 2.1 「字段級(field level) 約束」

例 2.1. 字段級(field level) 約束

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

當約束被定義在字段上的時候, 這個字段的值是經過字段訪問策略來獲取並驗證的. 也就是說Bean Validation的實現者會直接訪問這個實例變量而不會調用屬性的訪問器(getter) 即便這個方法存在.

注意

這個字段的訪問級別( private, protected 或者 public) 對此沒有影響.

注意

靜態字段或者屬性是不會被校驗的.

2.1.2. 屬性級別約束

若是你的模型遵循JavaBeans規範的話, 你還能夠把約束標註在屬性上. 例 2.2 「屬性級約束」例 2.1 「字段級(field level) 約束」的惟一不一樣就是它的約束是定義在屬性級別上的.

注意

若是要定義約束在屬性級別上的話,那麼只能定義在訪問器(getter)上面,不能定義在修改器(setter)上.

例 2.2. 屬性級約束

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

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

提示

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

2.1.3. 類級別約束

最後, 一個約束也可以被放在類級別上. 當一個約束被標註在一個類上的時候,這個類的實例對象被傳遞給ConstraintValidator. 當須要同時校驗多個屬性來驗證一個對象或者一個屬性在驗證的時候須要另外的屬性的信息的時候, 類級別的約束會頗有用. 在例 2.3 「類級別約束」中, 咱們給類Car添加了一個passengers的屬性. 而且咱們還標註了一個PassengerCount約束在類級別上. 稍後會看到咱們是如何建立這個自定義的約束的(第 3 章 建立本身的約束規則). 如今,咱們能夠知道,PassengerCount會保證這個車裏乘客的數量不會超過它的座位數.

例 2.3. 類級別約束

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

2.1.4. 約束繼承

若是要驗證的對象繼承於某個父類或者實現了某個接口,那麼定義在父類或者接口中的約束會在驗證這個對象的時候被自動加載,如同這些約束定義在這個對象所在的類中同樣. 讓咱們來看看下面的示例:

例 2.4. 約束繼承

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

咱們有了一個新的RentalCar類繼承自前面咱們已經見到的Car, 這個子類中增長了一個rentalStation屬性. 若是校驗一個RentalCar的實例對象, 那麼不只會驗證屬性rentalStation上的 @NotNull約束是否合法,還會校驗父類中的manufacturer屬性.

若是類Car是一個接口類型的話也是同樣的效果.

若是類RentalCar 重寫了父類CargetManufacturer()方法, 那麼定義在父類的這個方法上的約束和子類這個方法上定義的約束都會被校驗.

2.1.5. 對象圖

Bean Validation API不只可以用來校驗單個的實例對象,還可以用來校驗完整的對象圖.要使用這個功能,只須要在一個有關聯關係的字段或者屬性上標註@Valid. 這樣,若是一個對象被校驗,那麼它的全部的標註了@Valid的關聯對象都會被校驗. 請看例 2.6 「Adding a driver to the car」.

例 2.5. Class Person

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

例 2.6. Adding a driver to the car

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

若是校驗Car的實例對象的話,由於它的driver屬性標註了@Valid, 那麼關聯的Person也會被校驗. 因此,若是對象Personname屬性若是是null的話,那麼校驗會失敗.

關聯校驗也適用於集合類型的字段, 也就是說,任何下列的類型:

  • 數組

  • 實現了java.lang.Iterable接口( 例如CollectionList 和 Set)

  • 實現了java.util.Map接口

若是標註了@Valid, 那麼當主對象被校驗的時候,這些集合對象中的元素都會被校驗.

例 2.7. Car with a list of passengers

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

當校驗一個Car的實例的時候,若是passengers list中包含的任何一個Person對象沒有名字的話,都會致使校驗失敗(a ConstraintValidation will be created).

注意

對象圖校驗的時候是會被忽略null值的.

2.2. 校驗約束

Validator 是Bean Validation中最主要的接口, 咱們會在第 5.1 節 「Configuration 和 ValidatorFactory」中詳細介紹如何獲取一個Validator的實例, 如今先讓咱們來看看如何使用Validator接口中的各個方法.

2.2.1. 獲取一個Validator的實例

對一個實體對象驗證以前首先須要有個Validator對象, 而這個對象是須要經過Validation 類和 ValidatorFactory來建立的. 最簡單的方法是調用Validation.buildDefaultValidatorFactory() 這個靜態方法.

例 2.8. Validation.buildDefaultValidatorFactory()

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

第 5 章 Bootstrapping介紹了其餘的獲取Validator實例的方法. 如今咱們的目標是學習如何使用Validator 來校驗實體對象.

2.2.2. Validator中的方法

Validator中有三個方法可以被用來校驗整個實體對象或者實體對象中的屬性.

這三個方法都會返回一個Set<ConstraintViolation>對象, 若是整個驗證過程沒有發現問題的話,那麼這個set是空的, 不然, 每一個違反約束的地方都會被包裝成一個ConstraintViolation的實例而後添加到set當中.

全部的校驗方法都接收零個或多個用來定義這次校驗是基於哪一個校驗組的參數. 若是沒有給出這個參數的話, 那麼這次校驗將會基於默認的校驗組 (javax.validation.groups.Default). 第 2.3 節 「校驗組」

2.2.2.1. validate

使用validate()方法對一個給定的實體對象中定義的全部約束條件進行校驗 (例 2.9 「Validator.validate() 使用方法」 ).

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

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

2.2.2.2. validateProperty

經過validateProperty()能夠對一個給定實體對象的單個屬性進行校驗. 其中屬性名稱須要符合JavaBean規範中定義的屬性名稱.

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

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

例如, Validator.validateProperty能夠被用在把Bean Validation集成進JSF 2中的時候使用 (請參考 第 7.4 節 「展現層校驗」).

2.2.2.3. validateValue

經過validateValue() 方法,你可以校驗若是把一個特定的值賦給一個類的某一個屬性的話,是否會違反此類中定義的約束條件.

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

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

注意

validateProperty() 和 validateValue() 會忽略被驗證屬性上定義的@Valid.

2.2.3. ConstraintViolation 中的方法

如今是時候看看究竟ConstraintViolation是什麼了. ConstraintViolation中包含了不少方法可以幫你快速定位到底是什麼致使了校驗失敗.表 2.1 「ConstraintViolation 中的方法」 列出了這些方法:

表 2.1. ConstraintViolation 中的方法

方法名 做用 示例 (請參考例 2.9 「Validator.validate() 使用方法」)
getMessage() 獲取(通過翻譯的)校驗錯誤信息 may not be null
getMessageTemplate() 獲取錯誤信息模版 {javax.validation.constraints.NotNull.message}
getRootBean() 獲取被校驗的根實體對象 car
getRootBeanClass() 獲取被校驗的根實體類. Car.class
getLeafBean() 若是約束是添加在一個bean(實體對象)上的,那麼則返回這個bean的實例, 若是是約束是定義在一個屬性上的, 則返回這個屬性所屬的bean的實例對象. car
getPropertyPath() 從被驗證的根對象到被驗證的屬性的路徑.  
getInvalidValue() 卻是校驗失敗的值. passengers
getConstraintDescriptor() 致使校驗失敗的約束定義.  

2.2.4. 驗證失敗提示信息解析

第 3 章 建立本身的約束規則中,咱們會看到,每一個約束定義中都包含有一個用於提示驗證結果的消息模版, 而且在聲明一個約束條件的時候,你能夠經過這個約束中的message屬性來重寫默認的消息模版, 能夠參考例 2.13 「Driver」. 若是在校驗的時候,這個約束條件沒有經過,那麼你配置的MessageInterpolator會被用來當成解析器來解析這個約束中定義的消息模版, 從而獲得最終的驗證失敗提示信息. 這個解析器會嘗試解析模版中的佔位符( 大括號括起來的字符串 ). 其中, Hibernate Validator中默認的解析器 (MessageInterpolator) 會先在類路徑下找名稱爲ValidationMessages.propertiesResourceBundle, 而後將佔位符和這個文件中定義的resource進行匹配,若是匹配不成功的話,那麼它會繼續匹配Hibernate Validator自帶的位於/org/hibernate/validator/ValidationMessages.propertiesResourceBundle, 依次類推,遞歸的匹配全部的佔位符.

由於大括號{ 在這裏是特殊字符,因此,你能夠經過使用反斜線來對其進行轉義, 例如:

  • \{ 被轉義成 {

  • \} 被轉義成 }

  • \\ 被轉義成 \

若是默認的消息解析器不可以知足你的需求,那麼你也能夠在建立ValidatorFactory的時候, 將其替換爲一個你自定義的MessageInterpolator, 具體請參考 第 5 章 Bootstrapping.

2.3. 校驗組

校驗組可以讓你在驗證的時候選擇應用哪些約束條件. 這樣在某些狀況下( 例如嚮導 ) 就能夠對每一步進行校驗的時候, 選取對應這步的那些約束條件進行驗證了. 校驗組是經過可變參數傳遞給validatevalidateProperty 和 validateValue的. 讓咱們來看個例子, 這個實例擴展了上面的Car類,又爲其添加了一個新的Driver. 首先, 類Person (例 2.12 「Person」) 的name屬性上定義了一個@NotNull 的約束條件. 由於沒有明確指定這個約束條件屬於哪一個組,因此它被歸類到默認組 (javax.validation.groups.Default).

注意

若是某個約束條件屬於多個組,那麼各個組在校驗時候的順序是不可預知的. 若是一個約束條件沒有被指明屬於哪一個組,那麼它就會被歸類到默認組(javax.validation.groups.Default).

例 2.12. Person

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

接下來, 咱們讓類Driver (例 2.13 「Driver」) 繼承自類Person. 而後添加兩個屬性,分別是age 和 hasDrivingLicense. 對於一個司機來講, 要開車的話, 必須知足兩個條件, 年滿18週歲 (@Min(18)) 和你的有駕照(@AssertTrue). 這兩個約束條件分別定義在那兩個屬性上, 而且把他們都歸於DriverChecks組. 經過例 2.14 「Group interfaces」, 你能夠看到, "DriverChecks組" 就是一個簡單的標記接口. 使用接口( 而不是字符串) 能夠作到類型安全,而且接口比字符串更加對重構友好, 另外, 接口還意味着一個組能夠繼承別的組.

例 2.13. Driver

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

例 2.14. Group interfaces

public interface DriverChecks { } public interface CarChecks { }

最後, 咱們給Car class (例 2.15 「Car」) 添加一個passedVehicleInspection的屬性,來表示這個車是否經過了上路檢查.

例 2.15. Car

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

如今, 在咱們的例子中有三個不一樣的校驗組, Person.name, Car.manufacturer, Car.licensePlate 和 Car.seatCount都屬於默認(Default) 組, Driver.age 和 Driver.hasDrivingLicense 從屬於 DriverChecks組, 而Car.passedVehicleInspection 在CarChecks組中. 例 2.16 「Drive away」演示瞭如何讓Validator.validate驗證不一樣的組來獲得不一樣的校驗結果.

例 2.16. Drive away

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

首先咱們建立一輛汽車而後在沒有明確指定使用哪一個校驗組的狀況下校驗它, 能夠看到即便passedVehicleInspection的默認值是false也不會校驗出錯誤來. 由於定義在這個屬性上的約束條件並不屬於默認的校驗組, 接下來,咱們來校驗CarChecks這個組, 這樣就會發現car違反了約束條件, 必須讓這個車先經過檢測. 接下來,咱們給這個車增長一個司機, 而後在基於DriverChecks來校驗, 會發現由於這個司機由於尚未經過駕照考試, 因此又一次獲得了校驗錯誤, 若是咱們設置passedDrivingTest屬性爲true以後, DriverChecks組的校驗就經過了.

最後, 讓咱們再來校驗全部的組中定義的約束條件,能夠看到全部的約束條件都經過了驗證.

2.3.1. 校驗組序列

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

注意

若是這個校驗組序列中有一個約束條件沒有經過驗證的話, 那麼此約束條件後面的都不會再繼續被校驗了.

例 2.17. 標註了@GroupSequence的接口

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

警告

一個校驗組序列中包含的校驗組和這個校驗組序列不能形成直接或者間接的循環引用. 包括校驗組繼承. 若是形成了循環引用的話, 會致使GroupDefinitionException異常.

例 2.18 「校驗組序列的用法」展現了校驗組序列的用法.

例 2.18. 校驗組序列的用法

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

2.3.2. 對一個類重定義其默認校驗組

2.3.2.1. @GroupSequence

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

例 2.19. RentalCar with @GroupSequence

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

例 2.20. testOrderedChecksWithRedefinedDefault

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

注意

由於不能在校驗組和校驗組序列中有循環依賴關係, 因此, 若是你想重定義一個類的默認組, 而且還想把Default組加入到這個重定義的序列當中的話, 則不能簡單的加入Default, 而是須要把被重定義的類加入到其中.

2.3.2.2. @GroupSequenceProvider

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

例 2.21. RentalCar with @GroupSequenceProvider

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

例 . DefaultGroupSequenceProvider implementation

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

2.4. 內置的約束條件

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

2.4.1. Bean Validation constraints

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

注意

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

表 2.2. Bean Validation constraints

Annotation Supported data types 做用 Hibernate metadata impact
@AssertFalse Booleanboolean Checks that the annotated element is false. 沒有
@AssertTrue Booleanboolean Checks that the annotated element is true. 沒有
@DecimalMax BigDecimalBigIntegerStringbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number. 被標註的值必須不大於約束中指定的最大值. 這個約束的參數是一個經過BigDecimal定義的最大值的字符串表示. 沒有
@DecimalMin BigDecimalBigIntegerStringbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number. 被標註的值必須不小於約束中指定的最小值. 這個約束的參數是一個經過BigDecimal定義的最小值的字符串表示. 沒有
@Digits(integer=, fraction=) BigDecimalBigIntegerStringbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number. Checks whether the annoted value is a number having up to integer digits and fraction fractional digits. 對應的數據庫表字段會被設置精度(precision)和準度(scale).
@Future java.util.Datejava.util.Calendar; Additionally supported by HV, if the Joda Time date/time API is on the class path: any implementations of ReadablePartial and ReadableInstant. 檢查給定的日期是否比如今晚. 沒有
@Max BigDecimalBigIntegerbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: String (the numeric value represented by a String is evaluated), any sub-type of Number. 檢查該值是否小於或等於約束條件中指定的最大值. 會給對應的數據庫表字段添加一個check的約束條件.
@Min BigDecimalBigIntegerbyteshortintlong and the respective wrappers of the primitive types. Additionally supported by HV: String (the numeric value represented by a String is evaluated), any sub-type of Number. 檢查該值是否大於或等於約束條件中規定的最小值. 會給對應的數據庫表字段添加一個check的約束條件.
@NotNull Any type Checks that the annotated value is not null. 對應的表字段不容許爲null.
@Null Any type Checks that the annotated value is null. 沒有
@Past java.util.Datejava.util.Calendar; Additionally supported by HV, if the Joda Time date/time API is on the class path: any implementations of ReadablePartial and ReadableInstant. 檢查標註對象中的值表示的日期比當前早. 沒有
@Pattern(regex=, flag=) String 檢查該字符串是否可以在match指定的狀況下被regex定義的正則表達式匹配. 沒有
@Size(min=, max=) StringCollectionMap and arrays Checks if the annotated element's size is between min and max (inclusive). 對應的數據庫表字段的長度會被設置成約束中定義的最大值.
@Valid Any non-primitive type 遞歸的對關聯對象進行校驗, 若是關聯對象是個集合或者數組, 那麼對其中的元素進行遞歸校驗,若是是一個map,則對其中的值部分進行校驗. 沒有

注意

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

2.4.2. Additional constraints

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

表 2.3. Custom constraints provided by Hibernate Validator

Annotation Supported data types 做用 Hibernate metadata impact
@CreditCardNumber String Checks that the annotated string passes the Luhn checksum test. Note, this validation aims to check for user mistakes, not credit card validity! See also Anatomy of Credit Card Numbers. 沒有
@Email String Checks whether the specified string is a valid email address. 沒有
@Length(min=, max=) String Validates that the annotated string is between min and max included. 對應的數據庫表字段的長度會被設置成約束中定義的最大值.
@NotBlank String Checks that the annotated string is not null and the trimmed length is greater than 0. The difference to @NotEmpty is that this constraint can only be applied on strings and that trailing whitespaces are ignored. 沒有
@NotEmpty StringCollectionMap and arrays Checks whether the annotated element is not null nor empty. 沒有
@Range(min=, max=) BigDecimalBigIntegerStringbyteshortintlong and the respective wrappers of the primitive types Checks whether the annotated value lies between (inclusive) the specified minimum and maximum. 沒有
@SafeHtml(whitelistType=, additionalTags=) CharSequence Checks whether the annotated value contains potentially malicious fragments such as <script/>. In order to use this constraint, the jsoup library must be part of the class path. With the whitelistType attribute predefined whitelist types can be chosen. You can also specify additional html tags for the whitelist with the additionalTags attribute. 沒有
@ScriptAssert(lang=, script=, alias=) Any type 要使用這個約束條件,必須先要保證Java Scripting API 即JSR 223 ("Scripting for the JavaTM Platform")的實如今類路徑當中. 若是使用的時Java 6的話,則不是問題, 若是是老版本的話, 那麼須要把 JSR 223的實現添加進類路徑. 這個約束條件中的表達式可使用任何兼容JSR 223的腳原本編寫. (更多信息請參考javadoc) 沒有
@URL(protocol=, host=, port=, regexp=, flags=) String Checks if the annotated string is a valid URL according to RFC2396. If any of the optional parameters protocolhost or port are specified, the corresponding URL fragments must match the specified values. The optional parameters regexp and flags allow to specify an additional regular expression (including regular expression flags) which the URL must match. 沒有

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

第 3 章 建立本身的約束規則

儘管Bean Validation API定義了一大堆標準的約束條件, 可是確定仍是有這些約束不能知足咱們需求的時候, 在這種狀況下, 你能夠根據你的特定的校驗需求來建立本身的約束條件.

3.1. 建立一個簡單的約束條件

按照如下三個步驟來建立一個自定義的約束條件

  • 建立約束標註

  • 實現一個驗證器

  • 定義默認的驗證錯誤信息

3.1.1. 約束標註

讓咱們來建立一個新的用來判斷一個給定字符串是否全是大寫或者小寫字符的約束標註. 咱們將稍後把它用在第 1 章 開始入門中的類CarlicensePlate字段上來確保這個字段的內容一直都是大寫字母.

首先,咱們須要一種方法來表示這兩種模式( 譯註: 大寫或小寫), 咱們可使用String常量, 可是在Java 5中, 枚舉類型是個更好的選擇:

例 3.1. 枚舉類型CaseMode, 來表示大寫或小寫模式.

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

如今咱們能夠來定義真正的約束標註了. 若是你之前沒有建立過標註(annotation)的話,那麼這個可能看起來有點嚇人, 但是其實沒有那麼難的 :)

例 3.2. 定義一個CheckCase的約束標註

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

一個標註(annotation) 是經過@interface關鍵字來定義的. 這個標註中的屬性是聲明成相似方法的樣式的. 根據Bean Validation API 規範的要求

  • message屬性, 這個屬性被用來定義默認得消息模版, 當這個約束條件被驗證失敗的時候,經過此屬性來輸出錯誤信息.

  • groups 屬性, 用於指定這個約束條件屬於哪(些)個校驗組(請參考第 2.3 節 「校驗組」). 這個的默認值必須是Class<?>類型到空到數組.

  • payload 屬性, Bean Validation API 的使用者能夠經過此屬性來給約束條件指定嚴重級別. 這個屬性並不被API自身所使用.

    提示

    經過payload屬性來指定默認錯誤嚴重級別的示例

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

    這樣, 在校驗完一個ContactDetails 的示例以後, 你就能夠經過調用ConstraintViolation.getConstraintDescriptor().getPayload()來獲得以前指定到錯誤級別了,而且能夠根據這個信息來決定接下來到行爲.

除了這三個強制性要求的屬性(message, groups 和 payload) 以外, 咱們還添加了一個屬性用來指定所要求到字符串模式. 此屬性的名稱value在annotation的定義中比較特殊, 若是隻有這個屬性被賦值了的話, 那麼, 在使用此annotation到時候能夠忽略此屬性名稱, 即@CheckCase(CaseMode.UPPER).

另外, 咱們還給這個annotation標註了一些(所謂的) 元標註( 譯註: 或"元模型信息"?, "meta annotatioins"):

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

  • @Retention(RUNTIME): 表示這個標註信息是在運行期經過反射被讀取的.

  • @Constraint(validatedBy = CheckCaseValidator.class): 指明使用那個校驗器(類) 去校驗使用了此標註的元素.

  • @Documented: 表示在對使用了@CheckCase的類進行javadoc操做到時候, 這個標註會被添加到javadoc當中.

提示

Hibernate Validator provides support for the validation of method parameters using constraint annotations (see 第 8.3 節 「Method validation」).

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

3.1.2. 約束校驗器

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

例 3.3. 約束條件CheckCase的驗證器

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

ConstraintValidator定義了兩個泛型參數, 第一個是這個校驗器所服務到標註類型(在咱們的例子中即CheckCase), 第二個這個校驗器所支持到被校驗元素到類型 (即String).

若是一個約束標註支持多種類型到被校驗元素的話, 那麼須要爲每一個所支持的類型定義一個ConstraintValidator,而且註冊到約束標註中.

這個驗證器的實現就很日常了, initialize() 方法傳進來一個所要驗證的標註類型的實例, 在本例中, 咱們經過此實例來獲取其value屬性的值,並將其保存爲CaseMode類型的成員變量供下一步使用.

isValid()是實現真正的校驗邏輯的地方, 判斷一個給定的String對於@CheckCase這個約束條件來講是不是合法的, 同時這還要取決於在initialize()中得到的大小寫模式. 根據Bean Validation中所推薦的作法, 咱們認爲null是合法的值. 若是null對於這個元素來講是不合法的話,那麼它應該使用@NotNull來標註.

3.1.2.1. ConstraintValidatorContext

例 3.3 「約束條件CheckCase的驗證器」 中的isValid使用了約束條件中定義的錯誤消息模板, 而後返回一個true 或者 false. 經過使用傳入的ConstraintValidatorContext對象, 咱們還能夠給約束條件中定義的錯誤信息模板來添加額外的信息或者徹底建立一個新的錯誤信息模板.

例 3.4. 使用ConstraintValidatorContext來自定義錯誤信息

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

例 3.4 「使用ConstraintValidatorContext來自定義錯誤信息」 演示了若是建立一個新的錯誤信息模板來替換掉約束條件中定義的默認的. 在本例中, 實際上經過調用ConstraintValidatorContext達到了一個使用默認消息模板的效果.

提示

在建立新的constraint violation的時候必定要記得調用addConstraintViolation, 只有這樣, 這個新的constraint violation纔會被真正的建立.

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

例 3.5. Adding new ConstraintViolation with custom property path

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

3.1.3. 校驗錯誤信息

最後, 咱們還須要指定若是@CheckCase這個約束條件驗證的時候,沒有經過的話的校驗錯誤信息. 咱們能夠添加下面的內容到咱們項目自定義的ValidationMessages.properties (參考 第 2.2.4 節 「驗證失敗提示信息解析」)文件中.

例 3.6. 爲CheckCase約束定義一個錯誤信息

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

若是發現校驗錯誤了的話, 你所使用的Bean Validation的實現會用咱們定義在@CheckCase中message屬性上的值做爲鍵到這個文件中去查找對應的錯誤信息.

3.1.4. 應用約束條件

如今咱們已經有了一個自定義的約束條件了, 咱們能夠把它用在第 1 章 開始入門中的Car類上, 來校驗此類的licensePlate屬性的值是否全都是大寫字母.

例 3.7. 應用CheckCase約束條件

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

最後,讓咱們用一個簡單的測試來檢測@CheckCase約束已經被正確的校驗了:

例 3.8. 演示CheckCase的驗證過程

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

3.2. 約束條件組合

例 3.7 「應用CheckCase約束條件」中咱們能夠看到, 類CarlicensePlate屬性上定義了三個約束條件. 在某些複雜的場景中, 可能還會有更多的約束條件被定義到同一個元素上面, 這可能會讓代碼看起來有些複雜, 另外, 若是在另外的類裏面還有一個licensePlate屬性, 咱們可能還要把這些約束條件再拷貝到這個屬性上, 可是這樣作又違反了 DRY 原則.

這個問題能夠經過使用組合約束條件來解決. 接下來讓咱們來建立一個新的約束標註@ValidLicensePlate, 它組合了@NotNull@Size 和 @CheckCase:

例 3.9. 建立一個約束條件組合ValidLicensePlate

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

咱們只須要把要組合的約束標註在這個新的類型上加以聲明 (注: 這正是咱們爲何把annotation types做爲了@CheckCase的一個target). 由於這個組合不須要額外的校驗器, 因此不須要聲明validator屬性.

如今, 在licensePlate屬性上使用這個新定義的"約束條件" (實際上是個組合) 和以前在其上聲明那三個約束條件是同樣的效果了.

例 3.10. 使用ValidLicensePlate組合約束

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

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

例 3.11. @ReportAsSingleViolation的用法

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

第 4 章 XML configuration

4.1. validation.xml

咱們可使用validation.xml來對Hibernate Validator進行配置. ValidationFactory在初始化的時候會在類路徑下尋找此文件,若是找到的話,就會應用其中定義的配置信息. 例 4.1 「validation-configuration-1.0.xsd」顯示了valiation.xml的xsd模型.

例 4.1. validation-configuration-1.0.xsd

 

例 4.2 「validation.xml」 列出了validation.xml中的一些經常使用的配置項.

例 4.2. validation.xml

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

警告

類路徑下面只能有一個validation.xml, 若是超過一個的話,會拋出異常.

validation.xml中全部的配置信息都是可選的, 例 4.2 「validation.xml」中就是列出了Hibernate Validator中的默認值. 若是類路徑當中存在有多個Bean Validation的實現的話, 那麼能夠經過default-provider節點指定使用那個Bean Validation的實現. message-interpolator, traversable-resolver 和 constraint-validator-factory能夠用來指定自定義的javax.validation.MessageInterpolatorjavax.validation.TraversableResolverjavax.validation.ConstraintValidatorFactory. 一樣的, 這些配置信息也能夠經過編程的方式調用javax.validation.Configuration來實現. 另外, 你能夠經過API的方式來重寫xml中的配置信息, 也能夠經過調用 Configuration.ignoreXmlConfiguration()來徹底的忽略掉xml的配置信息. 請參考第 5 章 Bootstrapping.

你能夠增長若干個constraint-mapping節點,在每一個裏面列出一個額外的xml文件用來定義約束規則, 具體請參考第 4.2 節 「映射約束」.

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

4.2. 映射約束

咱們也能夠經過xml來定義約束條件, 只須要這個xml符合例 4.3 「validation-mapping-1.0.xsd」中所定義的規範. 須要注意的是, 你必須把xml定義的約束列在validation.xmlconstraint-mapping節點中才能獲得處理.

例 4.3. validation-mapping-1.0.xsd


例 4.4 「constraints-car.xml」 顯示瞭如何經過xml定義的方式來給例 2.15 「Car」中的類Car 以及例 2.19 「RentalCar with @GroupSequence」 中的RentalCar定義約束條件.

例 4.4. constraints-car.xml

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

這個xml的定義基本上和經過編程方式差很少, 因此只須要簡單的解釋. 其中default-package屬性用來定義一個默認的包路徑, 若是下面指定的class不是全限定名稱的話,會自動加上這個默認的包路徑. 每一個xml文件均可以包含任意多個bean節點, 每一個對應一個要添加約束條件的實體類.

警告

每一個實體類只能在全部的xml映射文件中被定義一次, 不然會拋出異常.

經過添加ignore-annotations 屬性並將其設置爲true能夠忽略在對應bean上添加的約束標註信息, 這個屬性的默認值就是trueignore-annotations 屬性還能夠定義在class, fields 和 getter屬性上, 若是沒有明確指定的話, 那麼默認級別是bean (可參考 第 2.1 節 「定義約束」). constraint 節點用於添加一個約束條件到其父節點對應的元素上, 而且它須要經過annotation屬性來指定須要使用哪一個約束條件. 對於每一個約束條件中所須要的屬性, 其中, 由Bean Validation 規範規定的屬性(message, groups 和 payload) 能夠經過同名的子節點來定義, 而每一個約束條件中自定義的屬性, 則須要使用element節點來定義.

class節點一樣支持經過group-sequence節點來對一個類的默認校驗組進行重定義(請參考 第 2.3.2 節 「對一個類重定義其默認校驗組」) .

最後, 你還能夠經過constraint-definition節點來對一個指定的約束條件上綁定的校驗器(ConstraintValidator)進行修改. 此節點上的annotation對應要修改的約束條件, 而validated-by子節點中(按順序)列出要關聯到此約束條件上的校驗器( ConstraintValidator的實現類), 而include-existing-validator屬性若是是false的話,那麼默認定義在此約束條件上的校驗器將被忽略, 若是爲true, 那麼在xml中定義的校驗器會被添加在約束條件上默認定義的校驗器的後面.

第 5 章 Bootstrapping

第 5.1 節 「Configuration 和 ValidatorFactory」中咱們說道過, 最簡單的建立一個Validator實例的方法是經過Validation.buildDefaultValidatorFactory. 在本章中咱們會繼續介紹javax.validation.Validation中的其餘方法, 以及如何經過這些方法在Bean Validation初始化的時候對其進行配置的.

The different bootstrapping options allow, amongst other things, to bootstrap any Bean Validation implementation on the classpath. 一般, 一個服務的提供者是可以被Java Service Provider發現的. 對於Bean Validation的實現(服務提供者)來講, 他們的META-INF/services目錄下須要包含一個名爲javax.validation.spi.ValidationProvider的文件. 此文件中包含了一個ValidationProvider接口的實現類的全路徑名稱, 具體到Hibernate Validator來講, 就是org.hibernate.validator.HibernateValidator.

注意

若是當前類路徑下存在多個Bean Validation的實現, 那麼Validation.buildDefaultValidatorFactory()並不能保證具體那個實現會被使用. 若是想指定某一個的話, 請使用Validation.byProvider().

5.1. Configuration 和 ValidatorFactory

Validation類提供了三種方法來建立一個Validator的實例, 例 5.1 「Validation.buildDefaultValidatorFactory()」中顯示的是最簡單的方法.

例 5.1. Validation.buildDefaultValidatorFactory()

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

你也能夠經過Validation.byDefaultProvider()現獲取一個Configuration對象, 這樣能夠對要建立的Validator進行配置.

例 5.2. Validation.byDefaultProvider()

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

MessageInterpolatorTraversableResolver 和 ConstraintValidatorFactory會在後面詳細介紹.

最後, 你能夠指定使用哪一個Bean Validation的實現. 若是類路徑下存在多個Bean Validation的實現的話,這樣就頗有必要了. 例如, 若是你想使用Hibernate Validator來做爲內部實現來建立Validator的話:

例 5.3. Validation.byProvider( HibernateValidator.class )

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

提示

建立出來的Validator實例是線程安全的, 因此你能夠把它緩存起來.

5.2. ValidationProviderResolver

若是 Java Service Provider機制在你的環境中不可以正常工做, 或者你有特別的classloader設置的話, 你也能夠提供一個自定義的ValidationProviderResolver.例 5.4 「使用自定義的ValidationProviderResolver」顯示瞭如何在OSGi環境中插入自定義的provider resolver.

例 5.4. 使用自定義的ValidationProviderResolver

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

在這種狀況下, 你的OSGiServiceDiscoverer類須要實現ValidationProviderResolver接口:

例 5.5. ValidationProviderResolver接口

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

5.3. MessageInterpolator

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

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

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

例 5.6. 自定義的MessageInterpolator

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

提示

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

5.3.1. ResourceBundleLocator

一個廣泛的需求是你可能須要爲錯誤消息解析指定你本身的resource bundles. ResourceBundleMessageInterpolator是Hibernate Validator中默認的MessageInterpolator的實現, 它默認狀況下是經過ResourceBundle.getBundle來獲取resource bundle的. 不過, ResourceBundleMessageInterpolator也支持你指定一個自定義的ResourceBundleLocator實現來提供你本身的resource bundle. 例 5.7 「自定義的ResourceBundleLocator」提供了一個示例. 在這個例子中, 先經過 HibernateValidatorConfiguration.getDefaultResourceBundleLocator獲取默認的ResourceBundleLocator實現, 而後再用你自定義的實現把默認的包裝起來, 代理模式.

例 5.7. 自定義的ResourceBundleLocator

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

Hibernate Validator提供了兩個ResourceBundleLocator的實現 - PlatformResourceBundleLocator (默認) 和 AggregateResourceBundleLocator. 後者能夠定義一系列的resource bundle, 而後它會讀取這些文件, 而且把它們組合成一個. 更多信息請參考此類的javadoc 文檔.

5.4. TraversableResolver

到目前位置咱們尚未討論過TraversableResolver接口, 它的設計目的是在某些狀況下, 咱們可能不該該去獲取一個屬性的狀態. 最典型的狀況就是一個延遲加載的屬性或者與JPA中涉及到關聯關係的時候. 當驗證這兩種狀況的屬性的時候, 極可能會觸發一次對數據庫的查詢.Bean Validation正是經過TraversableResolver接口來控制可否訪問某一個屬性的 (例 5.8 「TraversableResolver接口」).

例 5.8. TraversableResolver接口

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

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

例 5.9. 自定義的TraversableResolver

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

5.5. ConstraintValidatorFactory

最後, 還有個配置項得提一下, 那就是ConstraintValidatorFactory類. Hibernate Validator中默認的ConstraintValidatorFactory須要一個無參的構造方法來初始化ConstraintValidator的實例(參考第 3.1.2 節 「約束校驗器」). 對於自定義的ConstraintValidatorFactory實現來講, 例如, 你可讓其支持對約束條件的依賴注入等功能. 配置使用這個自定義的ConstraintValidatorFactory的方法仍是老樣子(例 5.10 「自定義的ConstraintValidatorFactory」).

例 5.10. 自定義的ConstraintValidatorFactory

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

你須要實現此接口:

例 5.11. ConstraintValidatorFactory接口

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

警告

若是一個約束條件的實現須要依賴ConstraintValidatorFactory的某個特定的行爲(例如依賴注入或者沒有無參的構造方法等) 均可能致使不可移植.

注意

ConstraintValidatorFactory不該該緩存其建立的實例, 由於每一個實例均可能在其的初始化方法中被修改.

第 6 章 Metadata API

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

6.1. BeanDescriptor

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

提示

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

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

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

6.2. PropertyDescriptor

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

6.3. ElementDescriptor

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

例 6.1. Usage of ConstraintFinder

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

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

警告

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

6.4. ConstraintDescriptor

Last but not least, the ConstraintDescriptor interface describes a single constraint together with its composing constraints. Via an instance of this interface you get access to the constraint annotation and its parameters, as well as the groups the constraint is supposed to be applied on. It also also you to access the pass-through constraint payload (see 例 3.2 「定義一個CheckCase的約束標註」).

第 7 章 與其餘框架集成

Hibernate Validator 的設計初衷是在一個分層的應用程序中, 約束信息只須要被定義一次( 經過在領域模型上標註), 而後在不一樣的層中進行數據校驗.

7.1. OSGi

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

提示

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

Exported packages

  • org.hibernate.validator

  • org.hibernate.validator.constraints

  • org.hibernate.validator.cfg

  • org.hibernate.validator.cfg.context

  • org.hibernate.validator.cfg.defs

  • org.hibernate.validator.group

  • org.hibernate.validator.messageinterpolation

  • org.hibernate.validator.method

  • org.hibernate.validator.method.metadata

  • org.hibernate.validator.resourceloading

Imported packages

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

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

  • javax.xml.*

  • org.xml.sax.*

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

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

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

7.2. 與數據庫集成校驗

Hibernate Annotations (即 Hibernate 3.5.x) 會自動的把你定已在實體模型上的約束信息添加到其映射信息中. 例如, 假設你的一個實體類的屬性上有@NotNull的約束的話, 那麼Hibernate在生成建立此實體對應的表的DDL的時候, 會自動的給那個屬性對應的字段添加上not null.

若是由於某種緣由, 你不想使用這個特性, 那麼能夠將hibernate.validator.apply_to_ddl屬性設置爲false. 請參考???.

你也能夠限制這個DDL約束自動生成的特性只應用到一部分實體類. 只須要設置org.hibernate.validator.group.ddl屬性, 這個屬性的值是你想要應用此特性的實體類的全路徑名稱, 每一個以逗號分隔.

7.3. ORM集成

Hibernate Validator不只可以和Hibernate集成工做, 還可以和任何JPA的實現很好的一塊兒工做.

提示

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

7.3.1. 基於Hibernate事件模型的校驗

Hibernate Annotations (即 Hibernate 3.5.x) 中包含了一個的Hibernate 事件監聽器(譯註: 請閱讀Hibernate Core文檔瞭解Hibernate的事件模型) - org.hibernate.cfg.beanvalidation.BeanValidationEventListener - 來爲Hibernate Validator服務. 當一個PreInsertEventPreUpdateEvent 或 PreDeleteEvent事件發生的時候, 這個監聽器就能夠對該事件所涉及到的實體對象進行校驗, 若是校驗不經過的話, 則拋出異常. 默認狀況下, Hibernate在對每一個對象進行保存或者修改操做的時候,都會對其進行校驗, 而刪除操做則不會. 你能夠經過javax.persistence.validation.group.pre-persist, javax.persistence.validation.group.pre-update 和 javax.persistence.validation.group.pre-remove屬性來定義對應事件發生的時候, 具體要校驗哪(些)個校驗組, 這個屬性的值是要應用的校驗組類的全路徑, 使用逗號分隔. 例 7.1 「自定義BeanValidationEvenListener」顯示了這幾個屬性在Hibernate內部定義的默認值, 因此, 你不須要在你的應用中再重複定義了.

若是發生了違反約束條件的狀況, 該監聽器會拋出一個運行時的ConstraintViolationException異常, 此異常包含了一系列的ConstraintViolation對象用於描述每一個違反了約束條件的狀況.

若是類路徑上有Hibernate Validator, 則Hibernate Annotations (或 Hibernate EntityManager)會自動調用它, 若是你想避免這種狀況, 能夠設置javax.persistence.validation.mode屬性爲none.

注意

若是實體模型上沒有定義約束條件, 則不會有任何性能損耗.

若是你想在Hibernate Core中使用上面提到的事件監聽器, 則能夠在hibernate.cfg.xml中定義以下的配置信息:

例 7.1. 自定義BeanValidationEvenListener

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

7.3.2. JPA

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

對於JPA1來說, 你須要本身建立和註冊Hibernate Validator. 若是你是使用Hibernate EntityManager, 那麼你能夠把第 7.3.1 節 「基於Hibernate事件模型的校驗」中列出來的BeanValidationEventListener類添加到你的項目中, 而後再手工註冊它.

7.4. 展現層校驗

若是你正在使用JSF2或者JBoss Seam™,而且Hibernate Validator (Bean Validation) 在類路徑上的話, 那麼界面上的字段能夠被自動校驗. 例 7.2 「在JSF2中使用Bean Validation」顯示了一個在JSF頁面上使用f:validateBean標籤的實例. 更多的信息請參考Seam的文檔或者JSF2規範.

例 7.2. 在JSF2中使用Bean Validation

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

提示

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

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

The default is:

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

第 8 章 Hibernate Validator Specifics

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

注意

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

8.1. Public API

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

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

注意

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

表 8.1. Hibernate Validator public API

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

8.2. Fail fast mode

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

例 8.1. Enabling failFast via a property

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

// do some actual fail fast validation
...

例 8.2. Enabling failFast at the Configuration level

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

// do some actual fail fast validation
...

例 8.3. Enabling failFast at the ValidatorFactory level

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

Validator validator = factory.getValidator();

// do some non fail fast validation
...

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

// do fail fast validation
...

8.3. Method validation

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

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

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

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

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

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

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

注意

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

8.3.1. Defining method-level constraints

例 8.4 「Using method-level constraints」 demonstrates the definition of method-level constraints.

例 8.4. Using method-level constraints

public class RentalStation {

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

 

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

  • The renting customer may not be null

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

  • The rental duration must be at least one day

  • The returned Car instance may not be null

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

例 8.5. Cascaded validation of method-level constraints

public class RentalStation {

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

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

8.3.1.1. Using method constraints in type hierarchies

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

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

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

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

例 8.6. Illegal parameter constraint declarations

public class Car {

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

}

public class RentalCar extends Car {

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

}

public interface ICar {

    void drive(Person driver);

}

public class CarImpl implements ICar {

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

}

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

8.3.2. Evaluating method-level constraints

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

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

例 8.7. The MethodValidator interface

public interface MethodValidator {

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

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

例 8.8. Retrieving a MethodValidator instance

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

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

例 8.9. The MethodConstraintViolation type

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

    Method getMethod();

    Integer getParameterIndex();

    String getParameterName();

    Kind getKind();
}

注意

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

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

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

提示

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

8.3.3. Retrieving method-level constraint meta data

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

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

例 8.10. Retrieving meta data for method-level constraints

TypeDescriptor typeDescriptor = methodValidator.getConstraintsForType(RentalStation.class)

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

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

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

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

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

8.4. Programmatic constraint definition

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

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

例 8.11. Programmatic constraint definition

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

 

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

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

例 8.12. Programmatic constraint definition using createGeneric()

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

 

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

例 8.13. Programmatic definition of method constraints

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

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

例 8.14. Marking constraints for cascaded validation

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

 

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

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

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

 

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

例 8.16. Creating a Hibernate Validator specific configuration

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

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

 

8.5. Boolean composition for constraint composition

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

例 8.17. OR composition of constraints

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

提示

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

第 9 章 Annotation Processor

你碰到過下面這些讓人抓狂的狀況麼:

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

  • 對一個JavaBean的setter方法進行標註(而不是getter)

  • 對一個靜態的變量或者方法進行約束條件標註(這樣是不支持滴)

這樣的話, 你就應該看看Hibernate Validator 的約束處理器了. 它會被插入到編譯過程當中, 而後若是發現若是哪一個約束標註用錯了的話, 則彙報編譯錯誤.

注意

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

9.1. 前提條件

Hibernate Validator的標註處理器是基於JSR 269所定義的"可插入式標註處理API"的. 這個API從Java 6開始已是Java 平臺的一部分了, 因此請確保使用這個或者之後的版本.

9.2. 特性

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

  • 應用了約束標註的屬性的類型是否被該約束所支持

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

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

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

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

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

9.3. 配置項

Hibernate Validator標註處理器的行爲能夠經過表 9.1 「Hibernate Validator 標註處理器配置項」中列出的處理器配置項加以控制.

表 9.1. Hibernate Validator 標註處理器配置項

配置項 功能
diagnosticKind 控制編譯錯誤級別. 必須是枚舉類型javax.tools.Diagnostic.Kind中的值(字符串形式), 例如WARNING. 若是是ERROR的話, 那麼若是API檢測到約束信息應用的錯誤的話, 會讓編譯過程終止, 默認是ERROR.
methodConstraintsSupported Controls whether constraints are allowed at methods of any kind. Must be set to true when working with method level constraints as supported by Hibernate Validator. Can be set to false to allow constraints only at JavaBeans getter methods as defined by the Bean Validation API. Defaults to true.
verbose Controls whether detailed processing information shall be displayed or not, useful for debugging purposes. Must be either true or false. Defaults to false.

9.4. 使用標註處理器

本小節詳細介紹瞭如何把Hibernate Validator標註處理器與命令行編譯(javac, Ant, Maven)以及IDE (Eclipse, IntelliJ IDEA, NetBeans)集成.

9.4.1. 命令行編譯

9.4.1.1. javac

當使用命令行(javac)編譯的時候, 經過"processorpath"屬性指定下列jar:

  • validation-api-1.0.0.GA.jar

  • hibernate-validator-4.2.0.Final.jar

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

下面顯示了一個具體的示例. 這樣, 標註處理器就會自動被編譯器檢測到而且調用.

例 9.1. 在javac中使用標註處理器

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

9.4.1.2. Apache Ant

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

例 9.2. 在Ant中使用標註處理器

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

9.4.1.3. Maven

對於和Apache Maven集成來講咱們有不少選擇, 一般, 咱們能夠把Hibenrate Validator標註處理器做爲依賴添加到你的項目當中:

例 9.3. 添加HV 標註處理器爲依賴

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

這樣, 這個處理器就可以自動的被編譯器所調用. 雖然基本上能工做,可是仍是有一些缺點, 在某些狀況下, 標註處理器的輸出信息可能不可以被顯示出來. (請參考MCOMPILER-66).

另外的一個選擇是使用Maven Annotation Plugin. 不過在此文檔撰寫的時候, 這個插件尚未被上傳到任何一個普遍被使用的倉庫中. 因此, 你須要本身把這個插件本身的倉庫添加到你的settings.xml 或 pom.xml中:

例 9.4. 添加Maven Annotation Plugin的倉庫

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


如今, 禁用compiler插件所調用的標準的標註處理過程, 而後再經過定義一個execution來配置annotation plugin的運行, 還須要把Hibernate Validator標註處理器做爲該插件的依賴添加進去(這樣, 此標註處理器就不會被當成你的項目的依賴而出如今類路徑中了):

例 9.5. 配置Maven Annotation Plugin

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

9.4.2. IDE集成

9.4.2.1. Eclipse

請參考如下步驟來在Eclipse中使用標註處理器:

  • 右鍵點擊你的項目, 而後選擇"屬性"

  • 在"Java Compiler"頁面確認"編譯級別"設置的是"1.6". 不然的話是沒法使用標註處理器的.

  • 到"Java Compiler"下面的"Annotation Processing" 頁面, 而後選擇"啓用標註處理"(譯註: 個人電腦是英文版的, 因此真的不知道中文版的eclipse上, 這些翻譯成了什麼:(

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

    • validation-api-1.0.0.GA.jar

    • hibernate-validator-4.2.0.Final.jar

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

  • 確認工做空間從新編譯

如今你應該可以看到全部的標註錯誤都在編輯窗口中顯示出了錯誤標記,也都顯示在了"問題"視圖:

9.4.2.2. IntelliJ IDEA

請參考如下步驟來在IntelliJ IDEA (9.0及以上):中使用標註處理器:

  • 選擇 "File", 而後 "Settings",

  • 展開"Compiler"節點, 而後點擊"Annotation Processors"

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

  • 添加處理器的全路徑名稱org.hibernate.validator.ap.ConstraintValidationProcessor到"Annotation Processors"列表

  • 若是須要的話, 添加你的模塊到"Processed Modules"列表

從新編譯你的項目, 而後應該能看到關於約束標註的錯誤信息了:

9.4.2.3. NetBeans

從6.9這個版本開始, NetBeans也支持標註處理了. 能夠經過下面的步驟來啓用它:

  • 右鍵點擊你的項目, 而後選擇"屬性"

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

    • validation-api-1.0.0.GA.jar

    • hibernate-validator-4.2.0.Final.jar

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

  • 到"Build - Compiling"頁面選中"Enable Annotation Processing" 和 "Enable Annotation Processing in Editor", 而且指定標註處理器的全路徑名稱org.hibernate.validator.ap.ConstraintValidationProcessor.

全部的約束標註問題應該都會在編輯器裏面直接被標記出來了:

9.5. 已知問題

如下是截止到2010年五月咱們發現(但還沒有解決)的問題:

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

  • 有時候, 在eclipse裏面自定義的約束條件不可以被正確的檢查. 清理這個項目可能會有幫助. 這多是由於Eclipse中對 JSR 269 API的實現有問題, 可是還須要進一步的研究.

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

第 10 章 進一步閱讀

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

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

若是你還有什麼關於Hibernate Validator的問題或者想和咱們分享你的使用經驗,請使用Hibernate Validator Wiki或者去Hibernate Validator Forum發帖子(譯註:可是不要灌水:-D).

若是你發現了Hibernate Validator的bug,請在Hibernate's Jira中報告, 咱們很是歡迎您的反饋!

相關文章
相關標籤/搜索