Java EE 6核心特征:Bean Validation特性概述
Java EE 6 提出了 Bean Validation 規(guī)范,使用注解的方式對 Java Bean 進行約束驗證,不局限于某一層次或者某一編程模型,靈活易用。下邊將向您系統(tǒng)的介紹該規(guī)范的各種特性。
概述 Bean Validation 規(guī)范
Bean 是 Java Bean 的縮寫,在 Java 分層架構(gòu)的實際應(yīng)用中,從表示層到持久化層,每一層都需要對 Java Bean 進行業(yè)務(wù)符合性驗證,如圖 1 所示。然而對于同一個 Java Bean 的對象,在每一層都需要實現(xiàn)同樣的驗證邏輯時,這將是一項耗時且容易誘發(fā)錯誤的做法。Bean Validation 規(guī)范的目標就是避免多層驗證的重復(fù)性。事實上,開發(fā)者更傾向于將驗證規(guī)則直接放到 Java Bean 本身,使用注解的方式進行驗證規(guī)則的設(shè)計。
圖 1. Java 分層驗證結(jié)構(gòu)示意圖
JSR303 規(guī)范(Bean Validation 規(guī)范)提供了對 Java EE 和 Java SE 中的 Java Bean 進行驗證的方式。該規(guī)范主要使用注解的方式來實現(xiàn)對 Java Bean 的驗證功能,并且這種方式會覆蓋使用 XML 形式的驗證描述符,從而使驗證邏輯從業(yè)務(wù)代碼中分離出來,如圖 2 所示。
圖 2. Java Bean 驗證模型示意圖
JSR303 規(guī)范提供的 API 是 Java Bean 對象模型的一般擴展,它并不局限于某一層或者某一編程模型,在服務(wù)器端和客戶端都可使用,其最大的特點就是易用而且靈活。
Hibernate Validator4.0 是 JSR303 規(guī)范的參考實現(xiàn)之一,本文所有示例代碼均使用該參考實現(xiàn)。
下面給出一個 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
> set = validator.validate(employee); - for (ConstraintViolation
constraintViolation : set) { - System.out.println(constraintViolation.getMessage());
- }
- }
- }
運行該示例的輸出結(jié)果為:
- 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 進行了約束聲明,當(dāng)該 Java Bean 被實際使用時,相關(guān)的驗證器就會對該類的實例進行驗證確保其符合該約束聲明。完成 Java Bean 的驗證通??煞譃槿缦滤膫€步驟:
1. 約束注解的定義
2. 約束驗證規(guī)則(約束驗證器)
3. 約束注解的聲明
4. 約束驗證流程
本文第二大部分將詳細介紹約束注解的定義和約束驗證規(guī)則;第三大部分將詳細介紹約束注解的聲明和約束驗證流程;第四大部分將介紹 JSR303 規(guī)范提供的 API。
#p#
約束的定義約束注解
Bean Validation 規(guī)范對約束的定義包括兩部分,一是約束注解,清單 1 中的 @NotNull 就是約束注解;二是約束驗證器,每一個約束注解都存在對應(yīng)的約束驗證器,約束驗證器用來驗證具體的 Java Bean 是否滿足該約束注解聲明的條件。
在 Java Bean 中,對某一方法、字段、屬性或其組合形式等進行約束的注解,即為約束注解,如清單 2 所示:
清單 2:
- @NotNull(message = "The id of employee can not be null")
- private Integer id;
清單 2 的含義為:對于字段 id,在 Java Bean 的實例中值不能為空。對于每一個約束注解,在實際使用前必須有相關(guān)定義。JSR303 規(guī)范默認提供了幾種約束注解的定義(見表 1),我們也可以擴展規(guī)范提供的 API,實現(xiàn)符合自身業(yè)務(wù)需求的約束注解。
表 1. Bean Validation 規(guī)范內(nèi)嵌的約束注解定義
約束注解名稱 | 約束注解說明 |
@Null | 驗證對象是否為空 |
@NotNull | 驗證對象是否為非空 |
@AssertTrue | 驗證 Boolean 對象是否為 true |
@AssertFalse | 驗證 Boolean 對象是否為 false |
@Min | 驗證 Number 和 String 對象是否大等于指定的值 |
@Max | 驗證 Number 和 String 對象是否小等于指定的值 |
@DecimalMin | 驗證 Number 和 String 對象是否大等于指定的值,小數(shù)存在精度 |
@DecimalMax | 驗證 Number 和 String 對象是否小等于指定的值,小數(shù)存在精度 |
@Size | 驗證對象(Array,Collection,Map,String)長度是否在給定的范圍之內(nèi) |
@Digits | 驗證 Number 和 String 的構(gòu)成是否合法 |
@Past | 驗證 Date 和 Calendar 對象是否在當(dāng)前時間之前 |
@Future | 驗證 Date 和 Calendar 對象是否在當(dāng)前時間之后 |
@Pattern | 驗證 String 對象是否符合正則表達式的規(guī)則 |
約束注解和普通的注解一樣,一個典型的約束注解的定義應(yīng)該至少包括如下內(nèi)容(清單 3):
清單 3:
- @Target({ }) // 約束注解應(yīng)用的目標元素類型
- @Retention() // 約束注解應(yīng)用的時機
- @Constraint(validatedBy ={}) // 與約束注解關(guān)聯(lián)的驗證器
- public @interface ConstraintName{
- String message() default " "; // 約束注解驗證時的輸出消息
- Class>[] groups() default { }; // 約束注解在驗證時所屬的組別
- Class extends Payload>[] payload() default { }; // 約束注解的有效負載
- }
約束注解應(yīng)用的目標元素類型包括 METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER。METHOD 約束相關(guān)的 getter 方法;FIELD 約束相關(guān)的屬性;TYPE 約束具體的 Java Bean;ANNOTATION_TYPE 用在組合約束中;該規(guī)范同樣也支持對參數(shù)(PARAMETER)和構(gòu)造器(CONSTRUCTOR)的約束。
驗證時的組別屬性將在本文第三大部分中組與組序列中詳細介紹。
有效負載通常用來將一些元數(shù)據(jù)信息與該約束注解相關(guān)聯(lián),常用的一種情況是用負載表示驗證結(jié)果的嚴重程度。
清單 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 {};
- }
約束注解定義完成后,需要同時實現(xiàn)與該約束注解關(guān)聯(lián)的驗證器。約束驗證器的實現(xiàn)需要擴展 JSR303 規(guī)范提供的接口 javax.validation.ConstraintValidator。清單 5 給出該接口。
清單 5:
- public interface ConstraintValidatorextends Annotation, T> {
- void initialize(A constraintAnnotation);
- boolean isValid(T value, ConstraintValidatorContext context);
- }
該接口有兩個方法,方法 initialize 對驗證器進行實例化,它必須在驗證器的實例在使用之前被調(diào)用,并保證正確初始化驗證器,它的參數(shù)是約束注解;方法 isValid 是進行約束驗證的主體方法,其中 value 參數(shù)代表需要驗證的實例,context 參數(shù)代表約束執(zhí)行的上下文環(huán)境。
對于清單 4 定義的約束注解,清單 6 給出了與該注解對應(yīng)的驗證器的實現(xiàn)。
清單 6:
- public class NotEmptyValidator implements ConstraintValidator
{ - 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;
- }
- }
至此,一個可以聲明并使用的約束注解已經(jīng)定義完畢,清單 7 將給出該約束注解在實際程序中的使用。為節(jié)省篇幅,這里只給出針對清單 1 的增加和修改內(nèi)容,未給出全部的示例代碼,您可以在本文的附錄中獲得全部的代碼。
清單 7:
首先在清單 1 中的類 Employee 中加入字段 company 和相應(yīng)的 getter 和 setter 方法:
- @NotEmpty
- private String company;
然后在 main 函數(shù)中加入如下代碼清單:
- String company = new String();
- employee.setCompany(company);
再次運行該程序,輸出結(jié)果為:
- 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 規(guī)范的一個特性,多值約束(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();
- }
- }
實現(xiàn)多值約束只需要在定義約束注解的同時定義一個 List(@interface List{})。使用該約束注解時,Bean Validation 將 value 數(shù)組里面的每一個元素都處理為一個普通的約束注解,并對其進行驗證,所有約束條件均符合時才會驗證通過。
清單 9 定義了一個約束注解,它用來驗證某一字符串是否包含指定的內(nèi)容。
清單 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();
- }
- }
該約束注解對應(yīng)的驗證器如清單 10 所示:
清單 10:
- public class PatternOfStringValidator implements ConstraintValidator
{ - 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 以及相應(yīng)的 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 函數(shù)中加入如下代碼清單:
- String place = "C";
- employee.setPlace(place);
再次運行該程序,輸出結(jié)果為:
- 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",則輸出結(jié)果為:
- 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
可見,該約束會對聲明的兩個約束注解分別進行驗證,只要存在不符合約束驗證規(guī)則的 Java Bean 實例,就將產(chǎn)生相應(yīng)的驗證失敗信息。約束注解聲明的時候可以根據(jù)不同的約束值使用 message 參數(shù)給出不同的輸出信息。
組合約束
下面介紹 Bean Validation 規(guī)范中另一個重要的特性:組合約束。Bean Validation 規(guī)范允許將不同的約束進行組合來創(chuàng)建級別較高且功能較多的約束,從而避免原子級別約束的重復(fù)使用。如清單 4 定義的約束注解 @NotEmpty,是用來判斷一個字符串在非空的基礎(chǔ)上長度至少為 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 約束注解同樣的驗證結(jié)果。
#p#
約束的聲明和驗證流程
本文第二大部分介紹了如何定義約束注解和驗證器,本章主要介紹如何在 Java Bean 中應(yīng)用存在定義的約束注解,主要包括兩部分:一是約束的聲明;二是約束的驗證流程。
在需要進行約束的目標元素前面用注解的方式即可聲明約束,這意味著該目標元素必須滿足該約束的驗證條件。如清單 13 即在字段 id 上聲明了約束 @NotNull:
清單 13:
- @NotNull(message = "The id of employee can not be null")
- private Integer id;
該目標元素在具體實例中被賦值后,Bean Validation 就會調(diào)用相關(guān)的流程進行驗證。具體使用方式可以參見清單 14 所示,其中所涉及的接口將在本文第四大部分詳細介紹。
清單 14:
- ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
- Validator validator = vf.getValidator();
- Set
> set = validator.validate(JavaBeanInstance); - for (ConstraintViolation
constraintViolation : set) { - System.out.println(constraintViolation.getMessage());
- }
Bean Validation 規(guī)范對 Java Bean 的驗證流程如下:在實際使用中調(diào)用 Validator.validate(JavaBeanInstance) 方法后,Bean Validation 會查找在 JavaBeanInstance上所有的約束聲明,對每一個約束調(diào)用對應(yīng)的約束驗證器進行驗證,最后的結(jié)果由約束驗證器的 isValid 方法產(chǎn)生,如果該方法返回 true,則約束驗證成功,否則驗證失敗。驗證失敗的約束將產(chǎn)生約束違規(guī)對象(ConstraintViolation 的實例)并放到約束違規(guī)列表中。驗證完成后所有的驗證失敗信息均能在該列表中查找并輸出。
前提條件
Bean Validation 規(guī)范規(guī)定在對 Java Bean 進行約束驗證前,目標元素必須滿足以下條件:
- 如果驗證的是屬性(getter 方法),那么必須遵從 Java Bean 的命名習(xí)慣(JavaBeans 規(guī)范);
- 靜態(tài)的字段和方法不能進行約束驗證;
- 約束適用于接口和基類;
- 約束注解定義的目標元素可以是字段、屬性或者類型等;
- 可以在類或者接口上使用約束驗證,它將對該類或?qū)崿F(xiàn)該接口的實例進行狀態(tài)驗證;
- 字段和屬性均可以使用約束驗證,但是不能將相同的約束重復(fù)聲明在字段和相關(guān)屬性(字段的 getter 方法)上。
Object Graph 驗證
除了支持 Java Bean 的實例驗證外,Bean Validation 規(guī)范同樣支持 Object Graph 的驗證。Object Graph 即為對象的拓撲結(jié)構(gòu),如對象之間的引用關(guān)系。如果類 A 引用類 B,則在對類 A 的實例進行約束驗證時也需要對類 B 的實例進行約束驗證,這就是驗證的級聯(lián)性。當(dāng)對 Java 語言中的集合、數(shù)組等類型進行驗證時也需要對該類型的每一個元素進行驗證。
完成級聯(lián)驗證的方式就是使用 @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 的實例進行驗證時,只有當(dāng)在 Order 引用的對象 Person 前面聲明了注解 @Valid,才對 Person 中 name 字段的 @NotEmpty 注解進行驗證,否則將不予驗證。
組
Bean Validation 規(guī)范中一個重要的概念,就是組和組序列。組定義了約束的子集。對于一個給定的 Object Graph 結(jié)構(gòu),有了組的概念,則無需對該 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
> set = validator.validate(user,GroupA. class);- for (ConstraintViolation
constraintViolation : set) { - System.out.println(constraintViolation.getMessage());
- }
- }
在類 User 中需要驗證的字段上聲明驗證時所屬的組別屬性,如(groups=GroupA.class), 然后在 main 函數(shù)中調(diào)用 validator.validate(user,GroupA.class)) 方法,在此必須指定需要驗證的組別。如果不顯示指明,則是默認的組別。
如清單 16,驗證器只會驗證類 User 的 lastname 字段,如果使用 validator.validate(user)),則會使用 Default.class 組別,從而驗證 firstname 和 middlename 字段。
需要注意的是:組也有繼承的屬性。對某一組別進行約束驗證的時候,也會對其所繼承的基類進行驗證。
組可以進行隱式定義,其好處是可以不必在約束聲明的時候顯式聲明組別屬性,如清單 16 中的(groups=GroupA.class)。清單 17 給出了一個隱式定義的組接口(Animal),其中包含對相應(yīng)屬性(getter 方法)的約束聲明。相應(yīng)的 Java Bean(Dog)實現(xiàn)了該接口。
清單 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
> set = validator.validate(dog,Animal. class);- for (ConstraintViolation
constraintViolation : set) { - System.out.println(constraintViolation.getMessage());
- }
- }
輸出結(jié)果為:
- this string may be empty
- this string may be empty
組序列
默認情況下,不同組別的約束驗證是無序的,然而在某些情況下,約束驗證的順序卻很重要,如下面兩個例子:(1)第二個組中的約束驗證依賴于一個穩(wěn)定狀態(tài)來運行,而這個穩(wěn)定狀態(tài)是由第一個組來進行驗證的。(2)某個組的驗證比較耗時,CPU 和內(nèi)存的使用率相對比較大,最優(yōu)的選擇是將其放在最后進行驗證。因此,在進行組驗證的時候尚需提供一種有序的驗證方式,這就提出了組序列的概念。
一個組可以定義為其他組的序列,使用它進行驗證的時候必須符合該序列規(guī)定的順序。在使用組序列驗證的時候,如果序列前邊的組驗證失敗,則后面的組將不再給予驗證。
清單 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
> set = validator.validate(user,Group. class);- for (ConstraintViolation
constraintViolation : set) { - System.out.println(constraintViolation.getMessage());
- }
- }
清單 19 中 main 函數(shù)的輸出結(jié)果為:
- middlename may be empty
- firstname may be empty
從輸出結(jié)果可以看出,該驗證將不再為屬于 GroupA 和 GroupB 的約束進行驗證,因為屬于組序列(Group.class)中前面位置的 Default 組驗證失敗。只有當(dāng)在 main 函數(shù)加入如下代碼片段使屬于 Default 組別的驗證通過后,方可進行后續(xù)組別(GroupA,GroupB)的驗證。
- user.setFirstname("firstname");
- user.setMiddlename("midlename");
穿透驗證器(TrversableProperty)
穿透驗證器主要適用于 JPA 規(guī)范,JPA 規(guī)范提供一種惰性連接屬性,允許實體對象的某些字段被延遲加載,這些被延遲加載的字段需要 JPA 從底層數(shù)據(jù)庫中獲取。Bean Validation 規(guī)范通過 TraversableResolver 接口來控制這類字段的存取性。在實際使用中需要先調(diào)用該接口中的 isReachable() 方法,如果返回 true,則證明該屬性是可存取的,方可進行屬性的約束驗證。同樣,在進行級聯(lián)驗證時,也需要首先確定所引用的字段或者屬性的可存取性方可進行約束的級聯(lián)驗證。
#p#
Bean Validation 規(guī)范接口及其可擴展的實現(xiàn)
本文前面的章節(jié)介紹了如何定義約束注解以及如何使用約束進行 Java Bean 驗證。對于第三部分中提到的約束驗證流程中的接口,本章將給予詳細的介紹。
Bean Validation 規(guī)范允許用戶定制個性化的約束驗證,并給出了 4 大類接口供擴展使用。本章將結(jié)合 Bean Validation 規(guī)范的參考實現(xiàn) Hibernate Validator4.0 進行說明。圖 3 給出了 Bean
Validation 規(guī)范的 API 以及 Hibernate4.0 相關(guān)實現(xiàn)之間的關(guān)系示意圖。
圖 3. Bean Validation 接口以及 Hibernate4.0 接口實現(xiàn)示意圖(查看大圖)
1. Bootstrapping 相關(guān)接口
Bootstrapping 相關(guān)接口提供 ValidatorFactory 對象,該對象負責(zé)創(chuàng)建 Validator(驗證器)實例,該實例即是 Bean Validation 客戶端用來進行約束驗證的主體類。Bootstrapping 相關(guān)接口主要包括 5 類,如表 2 所示:
表 2. Bootstrapping 相關(guān)接口及其作用:
接口 | 作用 |
javax.validation.validation | Bean Validation 規(guī)范的 API 默認提供該類,是整個 API 的入口,用來產(chǎn)生 Configuraton 對象實例,并啟動環(huán)境中 ValidationProvider 的具體實現(xiàn)。 |
javax.validation.ValidationProviderResolver | 返回執(zhí)行上下文環(huán)境中所有的 BeanValidationProviders 的列表,并對每一個 BeanValidationProvider 產(chǎn)生一個對象實例。BeanValidation 規(guī)范提供一個默認的實現(xiàn)。 |
javax.validation.spi.ValidationProvider | 具體的 BeanValidationProvider 實現(xiàn)需要實現(xiàn)該接口。該接口用來生成具體的 Congfiguration 接口的實現(xiàn)。 |
javax.validation.Configuration | 收集上下文環(huán)境中的配置信息,主要用來計算如何給定正確的 ValidationProvider,并將其委派給 ValidatorFactory 對象。 |
javax.validation.ValidatorFactory | 從一個具體的 BeanValidationProvider 中構(gòu)建 Validator 的實例。 |
2. Validator 接口
該接口(javax.validation.Validator)定義了驗證實例的方法,主要包括三種,如表 2 所示:
表 3. Validator 接口中的方法及其作用
方法名 | 作用 |
該方法用于驗證一個給定的對象 | |
該方法用于驗證給定對象中的字段或者屬性 | |
該方法用于驗證給定對象中的屬性的具體值 |
上述兩類接口完成驗證器的初始化工作,下面使用清單 20 解釋上述接口,在本文的示例中均使用 Hibernat Validator4.0 作為參考實現(xiàn),因此上述兩類接口的具體實現(xiàn)均是 Hibernat Validator4.0 包中的類。
清單 20:
- ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
- Validator validator = vf.getValidator();
清單 20 使用默認的方式創(chuàng)建驗證工廠(ValidatorFactory),類 Validation 會檢索類路徑下面所有的 jar 文件,使用 ValidationProviderResolver 接口的默認實現(xiàn) DefaultValidationProviderResolver(Bean Validation 規(guī)范提供該類)查找 META-INF/services/ 目錄中的 javax.validation.spi.ValidationProvider 文件 , 在 Hibernate Validator4.0 中該文件中聲明 org.hibernate.validator.HibernateValidator 類為 ValidationProvider 的具體實現(xiàn),因此 Validation 調(diào)用 HibernateValidator 類創(chuàng)建 Configuration 接口的實例,在 Hibernate Validator4.0 中,該實例為 ConfigurationImpl。最后由 ConfigurationImpl 類產(chǎn)生 ValidatorFactory 的實例,在 HibernateValidator4.0 中為 ValidatorFactoryImpl 類。
如果類路徑中存在著多個該規(guī)范的實現(xiàn),這就要用到 Configuration 接口去顯示指定要使用的具體實現(xiàn),然后再產(chǎn)生 ValidatorFactory 的實例。如清單 21 所示:
清單 21:
- Configuration
config = - Validation.byProvider(HibernateValidator.class).configure();
- ValidatorFactory vf = config.buildValidatorFactory();
- Validator validator = vf.getValidator();
如果想實現(xiàn)符合自身業(yè)務(wù)邏輯的 BeanValidationProvider 檢索規(guī)則,只需要實現(xiàn)接口 ValidationProviderResolver,而不是僅使用規(guī)范提供的默認實現(xiàn)。如清單 22 所示:
清單 22:
- Configuration> config=Validation.byDefaultProvider().providerResolver(
- new MyValidationProviderResolver()).configure();
- ValidatorFactory vf = config.buildValidatorFactory();
- Validator validator = vf.getValidator();
清單 22 中 MyValidationProviderResolver 就是自定義的檢索規(guī)則,負責(zé)告訴 BeanValidation 如何在具體環(huán)境中進行 BeanValidationProvider 的查找。
3. ConstraintViolation 接口
該接口(javax.validation.ConstraintViolation)用來描述某一驗證的失敗信息。對某一個實體對象進行驗證的時候,會返回 ConstraintViolation 的集合,如清單 23 所示:
清單 23:
- Set
> set = validator.validate(employee); - for (ConstraintViolation
constraintViolation : set) { - System.out.println(constraintViolation.getMessage());
- }
MessageInterpolator 接口
該接口(javax.validation.MessageInterpolator)用來將驗證過程中的失敗消息以可讀的方式傳遞給客戶端使用者。Bean Validation 規(guī)范提供一個默認的消息解析接口,用戶可自定義符合自身業(yè)務(wù)需求的消息解析機制,只需實現(xiàn)該接口即可,如清單 24 所示。
清單 24:
- Configuration> config = Validation.byDefaultProvider().configure();
- config.messageInterpolator(new MyMessageInterpolator(config
- .getDefaultMessageInterpolator()));
其中 MyMessageInterpolator 就是自定義的消息解析器,用來完成特定的邏輯。
Bean Validation 規(guī)范的輸出消息默認從類路徑下的 ValidationMessage.properties 文件中讀取,用戶也可以在約束注解聲明的時候使用 message 屬性指定消息內(nèi)容。
#p#
結(jié)語
Bean Validation 規(guī)范使用注解的方式使 Java Bean 的驗證機制更加靈活而且高效。本文對該規(guī)范進行了簡單的介紹,旨在為 Java Bean 中業(yè)務(wù)邏輯驗證機制的程序開發(fā)提供一個有益的參考。
【編輯推薦】