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