spring jsr303--轉ibm developerwork

概述 Bean Validation 規範

Bean 是 Java Bean 的縮寫,在 Java 分層架構的實際應用中,從表示層到持久化層,每一層都需要對 Java Bean 進行業務符合性驗證,如圖 1 所示。然而對於同一個 Java Bean 的對象,在每一層都需要實現同樣的驗證邏輯時,這將是一項耗時且容易誘發錯誤的做法。Bean Validation 規範的目標就是避免多層驗證的重複性。事實上,開發者更傾向於將驗證規則直接放到 Java Bean 本身,使用註解的方式進行驗證規則的設計。

圖 1. Java 分層驗證結構示意圖
圖 1. Java 分層驗證結構示意圖

JSR303 規範(Bean Validation 規範)提供了對 Java EE 和 Java SE 中的 Java Bean 進行驗證的方式。該規範主要使用註解的方式來實現對 Java Bean 的驗證功能,並且這種方式會覆蓋使用 XML 形式的驗證描述符,從而使驗證邏輯從業務代碼中分離出來,如圖 2 所示。

圖 2. Java Bean 驗證模型示意圖
圖 2. Java Bean 驗證模型示意圖

JSR303 規範提供的 API 是 Java Bean 對象模型的一般擴展,它並不侷限於某一層或者某一編程模型,在服務器端和客戶端都可使用,其最大的特點就是易用而且靈活。

Hibernate Validator 4.0 是 JSR303 規範的參考實現,本文所有示例代碼均使用該參考實現。

下面給出一個 Bean Validation 的簡單示例(清單 1):

清單 1:
 public class Employee { 
 @NotNull(message = "The id of employee can not be null") 
 private Integer id; 

 @NotNull(message = "The name of employee can not be null") 
 @Size(min = 1,max = 10,message="The size of employee's name must between 1 and 10") 
 private String name; 

 public int getId() { 
 return id; 
 } 
 public void setId(int id) { 
 this.id = id; 
 } 
 public String getName() { 
 return name; 
 } 
 public void setName(String name) { 
 this.name = name; 
 } 
 public static void main(String[] args) { 
 Employee employee = new Employee(); 
 employee.setName("Zhang Guan Nan"); 
 ValidatorFactory vf = Validation.buildDefaultValidatorFactory(); 
 Validator validator = vf.getValidator(); 
 Set<ConstraintViolation<Employee>> set = validator.validate(employee); 
 for (ConstraintViolation<Employee> constraintViolation : set) { 
 System.out.println(constraintViolation.getMessage()); 
 } 
 } 
 }

運行該示例的輸出結果爲:

The size of employee's name must between 1 and 10

The id of employee can not be null

從示例中可以看出,Bean Validation 使用註解(@NotNull 和 @Size)的方式對字段 id 和 name 進行了約束聲明,當該 Java Bean 被實際使用時,相關的驗證器就會對該類的實例進行驗證確保其符合該約束聲明。完成 Java Bean 的驗證通常可分爲如下四個步驟:

  1. 約束註解的定義
  2. 約束驗證規則(約束驗證器)
  3. 約束註解的聲明
  4. 約束驗證流程

本文第二大部分將詳細介紹約束註解的定義和約束驗證規則;第三大部分將詳細介紹約束註解的聲明和約束驗證流程;第四大部分將介紹 JSR303 規範提供的 API。

約束的定義

約束註解

Bean Validation 規範對約束的定義包括兩部分,一是約束註解,清單 1 中的 @NotNull 就是約束註解;二是約束驗證器,每一個約束註解都存在對應的約束驗證器,約束驗證器用來驗證具體的 Java Bean 是否滿足該約束註解聲明的條件。

在 Java Bean 中,對某一方法、字段、屬性或其組合形式等進行約束的註解,即爲約束註解,如清單 2 所示:

清單 2:
 @NotNull(message = "The id of employee can not be null") 
 private Integer id;

清單 2 的含義爲:對於字段 id,在 Java Bean 的實例中值不能爲空。對於每一個約束註解,在實際使用前必須有相關定義。JSR303 規範默認提供了幾種約束註解的定義(見表 1),我們也可以擴展規範提供的 API,實現符合自身業務需求的約束註解。

表 1. Bean Validation 規範內嵌的約束註解定義
約束註解名稱 約束註解說明
@Null 驗證對象是否爲空
@NotNull 驗證對象是否爲非空
@AssertTrue 驗證 Boolean 對象是否爲 true
@AssertFalse 驗證 Boolean 對象是否爲 false
@Min 驗證 Number 和 String 對象是否大等於指定的值
@Max 驗證 Number 和 String 對象是否小等於指定的值
@DecimalMin 驗證 Number 和 String 對象是否大等於指定的值,小數存在精度
@DecimalMax 驗證 Number 和 String 對象是否小等於指定的值,小數存在精度
@Size 驗證對象(Array,Collection,Map,String)長度是否在給定的範圍之內
@Digits 驗證 Number 和 String 的構成是否合法
@Past 驗證 Date 和 Calendar 對象是否在當前時間之前
@Future 驗證 Date 和 Calendar 對象是否在當前時間之後
@Pattern 驗證 String 對象是否符合正則表達式的規則

約束註解和普通的註解一樣,一個典型的約束註解的定義應該至少包括如下內容(清單 3):

清單 3:
 @Target({ })   // 約束註解應用的目標元素類型
 @Retention()   // 約束註解應用的時機
 @Constraint(validatedBy ={})  // 與約束註解關聯的驗證器
 public @interface ConstraintName{ 
 String message() default " ";   // 約束註解驗證時的輸出消息
 Class<?>[] groups() default { };  // 約束註解在驗證時所屬的組別
 Class<? extends Payload>[] payload() default { }; // 約束註解的有效負載
 }

約束註解應用的目標元素類型包括 METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER。METHOD 約束相關的 getter 方法;FIELD 約束相關的屬性;TYPE 約束具體的 Java Bean;ANNOTATION_TYPE 用在組合約束中;該規範同樣也支持對參數(PARAMETER)和構造器(CONSTRUCTOR)的約束。

驗證時的組別屬性將在本文第三大部分中組與組序列中詳細介紹。

有效負載通常用來將一些元數據信息與該約束註解相關聯,常用的一種情況是用負載表示驗證結果的嚴重程度。

清單 4 給出一個驗證字符串非空的約束註解的定義:

清單 4:
 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 
 @Retention(RUNTIME) 
 @Documented 
 @Constraint(validatedBy = {NotEmptyValidator.class}) 
 public @interface NotEmpty { 
 String message() default "this string may be empty"; 
 Class<?>[] groups() default { }; 
 Class<? extends Payload>[] payload() default {}; 
 }

約束註解定義完成後,需要同時實現與該約束註解關聯的驗證器。約束驗證器的實現需要擴展 JSR303 規範提供的接口 javax.validation.ConstraintValidator。清單 5 給出該接口。

清單 5:
 public interface ConstraintValidator<A extends Annotation, T> { 
 void initialize(A constraintAnnotation); 
 boolean isValid(T value, ConstraintValidatorContext context); 
 }

該接口有兩個方法,方法 initialize 對驗證器進行實例化,它必須在驗證器的實例在使用之前被調用,並保證正確初始化驗證器,它的參數是約束註解;方法 isValid 是進行約束驗證的主體方法,其中 value 參數代表需要驗證的實例,context 參數代表約束執行的上下文環境。

對於清單 4 定義的約束註解,清單 6 給出了與該註解對應的驗證器的實現。

清單 6:
 public class NotEmptyValidator implements ConstraintValidator<NotEmpty, String>{ 
 public void initialize(NotEmpty parameters) { 
 } 
 public boolean isValid(String string, 
    ConstraintValidatorContext constraintValidatorContext) { 
 if (string == null) return false; 
 else if(string.length()<1) return false; 
 else return true; 
 } 
 }

至此,一個可以聲明並使用的約束註解已經定義完畢,清單 7 將給出該約束註解在實際程序中的使用。爲節省篇幅,這裏只給出針對清單 1 的增加和修改內容,未給出全部的示例代碼,您可以在本文的附錄中獲得全部的代碼。

清單 7:
首先在清單 1 中的類 Employee 中加入字段 company 和相應的 getter 和 setter 方法:
 @NotEmpty 
 private String company; 
然後在 main 函數中加入如下代碼清單:
 String company = new String(); 
 employee.setCompany(company); 
再次運行該程序,輸出結果爲:
 The id of employee can not be null 
 this string may be empty 
 The size of employee's name must between 1 and 10

多值約束

下面介紹 Bean Validation 規範的一個特性,多值約束(Multiple Constraints):對於同一個目標元素,在進行約束註解聲明時可以同時使用不同的屬性達到對該目標元素進行多值驗證的目的。如清單 8 所示:

清單 8:
 public @interface ConstraintName{ 
 String message() default " "; 
 Class<?>[] groups() default { }; 
 Class<? extends Payload>[] payload() default { }; 
 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 
 @Retention(RUNTIME) 
 @Documented 
 @interface List { 
 ConstraintName[] value(); 
 } 
 }

實現多值約束只需要在定義約束註解的同時定義一個 List(@interface List{})。使用該約束註解時,Bean Validation 將 value 數組裏面的每一個元素都處理爲一個普通的約束註解,並對其進行驗證,所有約束條件均符合時纔會驗證通過。

清單 9 定義了一個約束註解,它用來驗證某一字符串是否包含指定的內容。

清單 9:
 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 
 @Retention(RUNTIME) 
 @Documented 
 @Constraint(validatedBy = PatternOfStringValidator.class) 
 public @interface PatternOfString { 
 String mustContainLetter(); 
 String message() default "this pattern may not be right"; 
 Class<?>[] groups() default { }; 
 Class<? extends Payload>[] payload() default {}; 

 @Target({ METHOD, FIELD, ANNOTATION_TYPE}) 
 @Retention(RUNTIME) 
 @interface List { 
 PatternOfString[] value(); 
 } 
 }

該約束註解對應的驗證器如清單 10 所示:

清單 10:
 public class PatternOfStringValidator implements ConstraintValidator
 <PatternOfString, String> { 
 private String letterIn; 
 public void initialize(PatternOfString parameters) { 
 this.letterIn=parameters.mustContainLetter(); 
 } 
 public boolean isValid(String string, 
 ConstraintValidatorContext constraintValidatorContext) { 
 if (string.contains(letterIn)) 
 return true; 
 return false; 
 } 
 }

如果想驗證某一字符串是否同時包含兩個子串,那麼多值約束就顯得比較重要了,清單 11 將詳細給出多值約束的使用。

清單 11:
在清單 1 中的類 Employee 中增加如下字段 place 以及相應的 getter 和 setter 方法:
 @PatternOfString.List({ 
 @PatternOfString(mustContainLetter = "CH", 
 message = "It does not belong to China"), 
 @PatternOfString(mustContainLetter="MainLand", 
 message="It does not belong to MainLand")}) 
 private String place; 
然後在 main 函數中加入如下代碼清單:
 String place = "C"; 
 employee.setPlace(place); 
再次運行該程序,輸出結果爲:
 It does not belong to MainLand 
 It does not belong to China 
 this string may be empty 
 The id of employee can not be null 
 The size of employee's name must between 1 and 10 
如果將 place 賦值爲 String place = "CHINA",則輸出結果爲:
 this string may be empty 
 The id of employee can not be null 
 It does not belong to MainLand

The size of employee's name must between 1 and 10

可見,該約束會對聲明的兩個約束註解分別進行驗證,只要存在不符合約束驗證規則的 Java Bean 實例,就將產生相應的驗證失敗信息。約束註解聲明的時候可以根據不同的約束值使用 message 參數給出不同的輸出信息。

組合約束

下面介紹 Bean Validation 規範中另一個重要的特性:組合約束。Bean Validation 規範允許將不同的約束進行組合來創建級別較高且功能較多的約束,從而避免原子級別約束的重複使用。如清單 4 定義的約束註解 @NotEmpty,是用來判斷一個字符串在非空的基礎上長度至少爲 1,其實際意義等同於 @NotNull 和 @Size(min=1)的組合形式,因此可以將 @NotEmpty 約束定義爲組合約束 NotEmpty2,如清單 12 所示:

清單 12:
 @NotNull 
 @Size(min = 1) 
 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 
 @Retention(RUNTIME) 
 @Documented 
 @Constraint(validatedBy = {NotEmptyValidator2.class}) 
 public @interface NotEmpty2 { 
 String message() default "this string may be empty"; 
 Class<?>[] groups() default { }; 
 Class<? extends Payload>[] payload() default {}; 

 @Target({ METHOD, FIELD, ANNOTATION_TYPE}) 
 @Retention(RUNTIME) 
 @interface List { 
 NotEmpty2[] value(); 
 } 
 }

實際使用中 @NotEmpty2 約束註解可以得到與 @NotEmpty 約束註解同樣的驗證結果。

約束的聲明和驗證流程

本文第二大部分介紹瞭如何定義約束註解和驗證器,本章主要介紹如何在 Java Bean 中應用存在定義的約束註解,主要包括兩部分:一是約束的聲明;二是約束的驗證流程。

在需要進行約束的目標元素前面用註解的方式即可聲明約束,這意味着該目標元素必須滿足該約束的驗證條件。如清單 13 即在字段 id 上聲明瞭約束 @NotNull:

清單 13:
 @NotNull(message = "The id of employee can not be null") 
 private Integer id;

該目標元素在具體實例中被賦值後,Bean Validation 就會調用相關的流程進行驗證。具體使用方式可以參見清單 14 所示,其中所涉及的接口將在本文第四大部分詳細介紹。

清單 14:
 ValidatorFactory vf = Validation.buildDefaultValidatorFactory(); 
 Validator validator = vf.getValidator(); 
 Set<ConstraintViolation<Employee>> set = validator.validate(JavaBeanInstance); 
 for (ConstraintViolation<Employee> constraintViolation : set) { 
 System.out.println(constraintViolation.getMessage()); 
 }

Bean Validation 規範對 Java Bean 的驗證流程如下:在實際使用中調用 Validator.validate(JavaBeanInstance) 方法後,Bean Validation 會查找在JavaBeanInstance上所有的約束聲明,對每一個約束調用對應的約束驗證器進行驗證,最後的結果由約束驗證器的 isValid 方法產生,如果該方法返回 true,則約束驗證成功,否則驗證失敗。驗證失敗的約束將產生約束違規對象(ConstraintViolation 的實例)並放到約束違規列表中。驗證完成後所有的驗證失敗信息均能在該列表中查找並輸出。

前提條件

Bean Validation 規範規定在對 Java Bean 進行約束驗證前,目標元素必須滿足以下條件:

  • 如果驗證的是屬性(getter 方法),那麼必須遵從 Java Bean 的命名習慣(JavaBeans 規範);
  • 靜態的字段和方法不能進行約束驗證;
  • 約束適用於接口和基類;
  • 約束註解定義的目標元素可以是字段、屬性或者類型等;
  • 可以在類或者接口上使用約束驗證,它將對該類或實現該接口的實例進行狀態驗證;
  • 字段和屬性均可以使用約束驗證,但是不能將相同的約束重複聲明在字段和相關屬性(字段的 getter 方法)上。

Object Graph 驗證

除了支持 Java Bean 的實例驗證外,Bean Validation 規範同樣支持 Object Graph 的驗證。Object Graph 即爲對象的拓撲結構,如對象之間的引用關係。如果類 A 引用類 B,則在對類 A 的實例進行約束驗證時也需要對類 B 的實例進行約束驗證,這就是驗證的級聯性。當對 Java 語言中的集合、數組等類型進行驗證時也需要對該類型的每一個元素進行驗證。

完成級聯驗證的方式就是使用 @Valid 註解,如清單 15 所示:

清單 15:
 public class Person { 
 @NotEmpty 
 private String name; 

 public String getName() { 
 return name; 
 } 
 public void setName(String name) { 
 this.name = name; 
 } 
 } 
 public class Order { 
 @Valid 
 private Person person; 

 public Person getPerson() { 
 return person; 
 } 
 public void setPerson(Person person) { 
 this.person = person; 
 } 
 }

在對 Order 的實例進行驗證時,只有當在 Order 引用的對象 Person 前面聲明瞭註解 @Valid,纔對 Person 中 name 字段的 @NotEmpty 註解進行驗證,否則將不予驗證。

Bean Validation 規範中一個重要的概念,就是組和組序列。組定義了約束的子集。對於一個給定的 Object Graph 結構,有了組的概念,則無需對該 Object Graph 中所有的約束進行驗證,只需要對該組定義的一個子集進行驗證即可。完成組別驗證需要在約束聲明時進行組別的聲明,否則使用默認的組 Default.class.

組使用接口的方式進行定義,清單 16 給出瞭如何定義組並使用組進行約束驗證。

清單 16:
 public interface GroupA {} 
 public class User { 
 @NotEmpty (message = "firstname may be empty") 
 private String firstname; 

 @NotEmpty(message = "middlename may be empty", groups = Default.class) 
 private String middlename; 

 @NotEmpty(message = "lastname may be empty",groups = GroupA.class) 
 private String lastname; 
 } 
 public static void main(String[] args){ 
 User user = new User(); 
 ValidatorFactory vf = Validation.buildDefaultValidatorFactory(); 
 Validator validator = vf.getValidator(); 
 Set<ConstraintViolation<User>> set = validator.validate(user,GroupA.class); 
 for (ConstraintViolation<User> constraintViolation : set) { 
 System.out.println(constraintViolation.getMessage()); 
 } 
 }

在類 User 中需要驗證的字段上聲明驗證時所屬的組別屬性,如(groups=GroupA.class), 然後在 main 函數中調用 validator.validate(user,GroupA.class)) 方法,在此必須指定需要驗證的組別。如果不顯示指明,則是默認的組別。

如清單 16,驗證器只會驗證類 User 的 lastname 字段,如果使用 validator.validate(user)),則會使用 Default.class 組別,從而驗證 firstname 和 middlename 字段。

需要注意的是:組也有繼承的屬性。對某一組別進行約束驗證的時候,也會對其所繼承的基類進行驗證。

組可以進行隱式定義,其好處是可以不必在約束聲明的時候顯式聲明組別屬性,如清單 16 中的(groups=GroupA.class)。清單 17 給出了一個隱式定義的組接口(Animal),其中包含對相應屬性(getter 方法)的約束聲明。相應的 Java Bean(Dog)實現了該接口。

清單 17:
 public interface Animal { 
 @NotEmpty String getName(); 
 @NotEmpty String getOwnerName(); 
 } 
 public class Dog implements Animal { 
 private String name; 
 private String ownername; 

 private String type; 

 public void setType(String type) { 
 this.type = type; 
 } 
 public String getName() { 
 return null; 
 } 
 public String getOwnerName() { 
 return null; 
 } 
 @NotEmpty(message = "type of the dog may be empty") 
 public String getType() { 
 return type; 
 } 
 }

這樣在對類 Dog 的實例進行驗證的時候,如果使用默認的組別(Default.class),則 name,ownername 和 type 都將進行驗證;如果使用 Animal 的組別,如清單 18 所示,則只會對 name 和 ownername 屬性進行驗證。

清單 18:
 public static void main(String[] args) { 
 Dog dog = new Dog(); 
 ValidatorFactory vf = Validation.buildDefaultValidatorFactory(); 
 Validator validator = vf.getValidator(); 
 Set<ConstraintViolation<Dog>> set = validator.validate(dog,Animal.class); 
 for (ConstraintViolation<Dog> constraintViolation : set) { 
 System.out.println(constraintViolation.getMessage()); 
 } 
 }

輸出結果爲:

this string may be empty

this string may be empty

組序列

默認情況下,不同組別的約束驗證是無序的,然而在某些情況下,約束驗證的順序卻很重要,如下面兩個例子:(1)第二個組中的約束驗證依賴於一個穩定狀態來運行,而這個穩定狀態是由第一個組來進行驗證的。(2)某個組的驗證比較耗時,CPU 和內存的使用率相對比較大,最優的選擇是將其放在最後進行驗證。因此,在進行組驗證的時候尚需提供一種有序的驗證方式,這就提出了組序列的概念。

一個組可以定義爲其他組的序列,使用它進行驗證的時候必須符合該序列規定的順序。在使用組序列驗證的時候,如果序列前邊的組驗證失敗,則後面的組將不再給予驗證。

清單 19 聲明瞭組 GroupA.class,GroupB.class 和 Group.class,其中 default,GroupA,GroupB 均爲 Group 的序列。

清單 19:
 public interface GroupA { 
 } 
 public interface GroupB { 
 } 
 @GroupSequence({Default.class, GroupA.class, GroupB.class}) 
 public interface Group { 
 } 
 public class User { 
 @NotEmpty (message = "firstname may be empty") 
 private String firstname; 

 @NotEmpty(message = "middlename may be empty", groups = Default.class) 
 private String middlename; 

 @NotEmpty(message = "lastname may be empty",groups = GroupA.class) 
 private String lastname; 

 @NotEmpty(message = "country may be empty",groups = GroupB.class) 
 private String country; 
 } 
 public static void main(String[] args){ 
 User user = new User(); 
 ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
 Validator validator = vf.getValidator(); 
 Set<ConstraintViolation<User>> set = validator.validate(user,Group.class); 
 for (ConstraintViolation<User> constraintViolation : set) { 
 System.out.println(constraintViolation.getMessage()); 
 } 
 }

清單 19 中 main 函數的輸出結果爲:

middlename may be empty

firstname may be empty

從輸出結果可以看出,該驗證將不再爲屬於 GroupA 和 GroupB 的約束進行驗證,因爲屬於組序列(Group.class)中前面位置的 Default 組驗證失敗。只有當在 main 函數加入如下代碼片段使屬於 Default 組別的驗證通過後,方可進行後續組別(GroupA,GroupB)的驗證。

 user.setFirstname("firstname"); 
 user.setMiddlename("midlename");

穿透驗證器(TrversableProperty)

穿透驗證器主要適用於 JPA 規範,JPA 規範提供一種惰性連接屬性,允許實體對象的某些字段被延遲加載,這些被延遲加載的字段需要 JPA 從底層數據庫中獲取。Bean Validation 規範通過 TraversableResolver 接口來控制這類字段的存取性。在實際使用中需要先調用該接口中的 isReachable() 方法,如果返回 true,則證明該屬性是可存取的,方可進行屬性的約束驗證。同樣,在進行級聯驗證時,也需要首先確定所引用的字段或者屬性的可存取性方可進行約束的級聯驗證。

Bean Validation 規範接口及其可擴展的實現

本文前面的章節介紹瞭如何定義約束註解以及如何使用約束進行 Java Bean 驗證。對於第三部分中提到的約束驗證流程中的接口,本章將給予詳細的介紹。

Bean Validation 規範允許用戶定製個性化的約束驗證,並給出了 4 大類接口供擴展使用。本章將結合 Bean Validation 規範的參考實現 Hibernate Validator4.0 進行說明。圖 3 給出了 Bean

Validation 規範的 API 以及 Hibernate4.0 相關實現之間的關係示意圖。

圖 3. Bean Validation 接口以及 Hibernate4.0 接口實現示意圖(查看大圖)
圖 3. Bean Validation 接口以及 Hibernate4.0 接口實現示意圖
  1. Bootstrapping 相關接口

    Bootstrapping 相關接口提供 ValidatorFactory 對象,該對象負責創建 Validator(驗證器)實例,該實例即是 Bean Validation 客戶端用來進行約束驗證的主體類。Bootstrapping 相關接口主要包括 5 類,如表 2 所示:

    表 2. Bootstrapping 相關接口及其作用
    接口 作用
    javax.validation.validation Bean Validation 規範的 API 默認提供該類,是整個 API 的入口,用來產生 Configuraton 對象實例,並啓動環境中 ValidationProvider 的具體實現。
    javax.validation.ValidationProviderResolver 返回執行上下文環境中所有的 BeanValidationProviders 的列表,並對每一個 BeanValidationProvider 產生一個對象實例。BeanValidation 規範提供一個默認的實現。
    javax.validation.spi.ValidationProvider 具體的 BeanValidationProvider 實現需要實現該接口。該接口用來生成具體的 Congfiguration 接口的實現。
    javax.validation.Configuration 收集上下文環境中的配置信息,主要用來計算如何給定正確的 ValidationProvider,並將其委派給 ValidatorFactory 對象。
    javax.validation.ValidatorFactory 從一個具體的 BeanValidationProvider 中構建 Validator 的實例。
  2. Validator 接口

    該接口(javax.validation.Validator)定義了驗證實例的方法,主要包括三種,如表 2 所示:

    表 3. Validator 接口中的方法及其作用
    方法名 作用
    <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) 該方法用於驗證一個給定的對象
    <T>Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>...groups) 該方法用於驗證給定對象中的字段或者屬性
    <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups) 該方法用於驗證給定對象中的屬性的具體值

    上述兩類接口完成驗證器的初始化工作,下面使用清單 20 解釋上述接口,在本文的示例中均使用 Hibernat Validator4.0 作爲參考實現,因此上述兩類接口的具體實現均是 Hibernat Validator4.0 包中的類。

    清單 20:
     ValidatorFactory vf = Validation.buildDefaultValidatorFactory(); 
     Validator validator = vf.getValidator();

    清單 20 使用默認的方式創建驗證工廠(ValidatorFactory),類 Validation 會檢索類路徑下面所有的 jar 文件,使用 ValidationProviderResolver 接口的默認實現 DefaultValidationProviderResolver(Bean Validation 規範提供該類)查找 META-INF/services/ 目錄中的 javax.validation.spi.ValidationProvider 文件 , 在 Hibernate Validator4.0 中該文件中聲明 org.hibernate.validator.HibernateValidator 類爲 ValidationProvider 的具體實現,因此 Validation 調用 HibernateValidator 類創建 Configuration 接口的實例,在 Hibernate Validator4.0 中,該實例爲 ConfigurationImpl。最後由 ConfigurationImpl 類產生 ValidatorFactory 的實例,在 HibernateValidator4.0 中爲 ValidatorFactoryImpl 類。

    如果類路徑中存在着多個該規範的實現,這就要用到 Configuration 接口去顯示指定要使用的具體實現,然後再產生 ValidatorFactory 的實例。如清單 21 所示:

    清單 21:
     Configuration<HibernateValidatorConfiguration> config = 
     Validation.byProvider(HibernateValidator.class).configure(); 
     ValidatorFactory vf = config.buildValidatorFactory(); 
     Validator validator = vf.getValidator();

    如果想實現符合自身業務邏輯的 BeanValidationProvider 檢索規則,只需要實現接口 ValidationProviderResolver,而不是僅使用規範提供的默認實現。如清單 22 所示:

    清單 22:
     Configuration<?> config=Validation.byDefaultProvider().providerResolver( 
     new MyValidationProviderResolver()).configure(); 
     ValidatorFactory vf = config.buildValidatorFactory(); 
     Validator validator = vf.getValidator();

    清單 22 中 MyValidationProviderResolver 就是自定義的檢索規則,負責告訴 BeanValidation 如何在具體環境中進行 BeanValidationProvider 的查找。

  3. ConstraintViolation 接口

    該接口(javax.validation.ConstraintViolation)用來描述某一驗證的失敗信息。對某一個實體對象進行驗證的時候,會返回 ConstraintViolation 的集合,如清單 23 所示:

    清單 23:
     Set<ConstraintViolation<Employee>> set = validator.validate(employee); 
     for (ConstraintViolation<Employee> constraintViolation : set) { 
     System.out.println(constraintViolation.getMessage()); 
     }
  4. MessageInterpolator 接口

    該接口(javax.validation.MessageInterpolator)用來將驗證過程中的失敗消息以可讀的方式傳遞給客戶端使用者。Bean Validation 規範提供一個默認的消息解析接口,用戶可自定義符合自身業務需求的消息解析機制,只需實現該接口即可,如清單 24 所示。

    清單 24:
     Configuration<?> config = Validation.byDefaultProvider().configure(); 
     config.messageInterpolator(new MyMessageInterpolator(config 
     .getDefaultMessageInterpolator()));

    其中 MyMessageInterpolator 就是自定義的消息解析器,用來完成特定的邏輯。

    Bean Validation 規範的輸出消息默認從類路徑下的 ValidationMessage.properties 文件中讀取,用戶也可以在約束註解聲明的時候使用 message 屬性指定消息內容。

結束語

Bean Validation 規範使用註解的方式使 Java Bean 的驗證機制更加靈活而且高效。本文對該規範進行了簡單的介紹,旨在爲 Java Bean 中業務邏輯驗證機制的程序開發提供一個有益的參考。

轉自:http://www.ibm.com/developerworks/cn/java/j-lo-beanvalid/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章