約束注解
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 ConstraintValidatorextends Annotation, T> {
- void initialize(A constraintAnnotation);
- boolean isValid(T value, ConstraintValidatorContext context);
- }
該接口有兩個方法,方法 initialize 對驗證器進行實例化,它必須在驗證器的實例在使用之前被調用,並保證正確初始化驗證器,它的參數是約束注解;方法 isValid 是進行約束驗證的主體方法,其中 value 參數代表需要驗證的實例,context 參數代表約束執行的上下文環境。
對於清單 4 定義的約束注解,清單 6 給出了與該注解對應的驗證器的實現。
清單 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;
- }
- }
至此,一個可以聲明並使用的約束注解已經定義完畢,清單 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
{ - 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 約束注解同樣的驗證結果。