創建自己的約束規則
盡管Bean Validation API定義了一大堆標准的約束條件, 但是肯定還是有這些約束不能滿足我們
需求的時候, 在這種情況下, 你可以根據你的特定的校驗需求來創建自己的約束條件.
一.創建一個簡單的約束條件
按照以下三個步驟來創建一個自定義的約束條件
•創建約束標注
•實現一個驗證器
•定義默認的驗證錯誤信息
1. 約束標注---讓我們來創建一個新的用來判斷一個給定字符串是否全是大寫或者小寫字符的約束標注.
首先,我們需要一種方法來表示這兩種模式( 譯注: 大寫或小寫), 我們可以使用 String 常量, 但是
在Java 5中, 枚舉類型是個更好的選擇:
package test02; public enum CaseMode { UPPER, LOWER; }
2.現在我們可以來定義真正的約束標注了. 如果你以前沒有創建過標注(annotation)的話參考
http://www.cnblogs.com/wangyang108/p/5668388.html
package test02; 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 屬性, 用於指定這個約束條件屬於哪(些)個校驗組.這
個的默認值必須是 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對方法的參數上使用約束注釋也提供的支持.
為了使用自定義的參數認證的注釋,ElementType.PARAMETER一定要被指定成@Target annotation
3.創建一個驗證器以讓我們的注釋生效--實現ConstraintValidator接口
package test02; 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 來標注.
4.現在來驗證一下
先建一個Po
package test02; public class User { @CheckCase(CaseMode.UPPER) private String name="wangyang"; }
然后進行驗證
package test02; 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 MyTest { private static Validator validator; /** * 獲取一個驗證器 */ @BeforeClass public static void setUp() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } @Test public void test01(){ User u=new User(); Set<ConstraintViolation<User>> validate = validator.validate(u); System.out.println(validate.size()); System.out.println(validate.iterator().next());
//ConstraintViolationImpl{interpolatedMessage='{com.mycompany.constraints.checkcase}', propertyPath=name, rootBeanClass=class test02.User, messageTemplate='{com.mycompany.constraints.checkcase}'} } }
4.ConstraintValidatorContext
上面的例子中的 isValid 使用了約束條件中定義的錯誤消息模板, 然
后返回一個 true 或者 false . 通過使用傳入的 ConstraintValidatorContext 對象, 我們還可以給約束
條件中定義的錯誤信息模板來添加額外的信息或者完全創建一個新的錯誤信息模板.
下面我們修改上述代碼
package test02; 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 isValid; } }
再進行驗證:
package test02; 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 MyTest { private static Validator validator; /** * 獲取一個驗證器 */ @BeforeClass public static void setUp() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } @Test public void test01(){ User u=new User(); Set<ConstraintViolation<User>> validate = validator.validate(u); System.out.println(validate.size()); System.out.println(validate.iterator().next()); //ConstraintViolationImpl{interpolatedMessage='{com.mycompany.constraints.CheckCase.message}', propertyPath=name, rootBeanClass=class test02.User, messageTemplate='{com.mycompany.constraints.CheckCase.message}'} } }
5.Adding new ConstraintViolation with custom property path(這一塊的東西真沒懂,暫不知道有什么用)
6.校驗錯誤信息
最后, 我們還需要指定如果 @CheckCase 這個約束條件驗證的時候,沒有通過的話的校驗錯誤信息.
我們可以添加下面的內容到我們項目自定義的 ValidationMessages.properties
例:
package test02; 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 "{test02.CheckCase.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; CaseMode value(); }
再定義一個ValidationMessages.properties
該文件中
test02.CheckCase.message = must be false {value}
運行測試結果:
package test02; 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 MyTest { private static Validator validator; /** * 獲取一個驗證器 */ @BeforeClass public static void setUp() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); } @Test public void test01(){ User u=new User(); Set<ConstraintViolation<User>> validate = validator.validate(u); System.out.println(validate.size()); //1 System.out.println(validate); //[ConstraintViolationImpl{interpolatedMessage='must be false UPPER', propertyPath=name, rootBeanClass=class test02.User, messageTemplate='{test02.CheckCase.message}'}] } }