Hibernate Validator實踐之一 入門篇

源自:http://www.jizhenfang.cn/?p=17

Hibernate Validator實踐之一 入門篇

在後臺的業務邏輯中,對數據值的校驗在各層都存在(展示層,業務層,數據訪問層等),並且各層校驗的規則又不盡相同,如下圖所示

注:該圖片來自於Hibernate Validator官網

在各層中重複的校驗邏輯既導致了不必要的資源消耗,還使得邏輯不夠單一(每層都夾雜着校驗的邏輯),JSR 303 Bean Validation就是在這種背景下產生的一個數據驗證的J2EE規範。而我們這篇文中將要介紹的Hibernate Validator則是JBoss社區開源的一個JSR 303  Bean Validation規範的優秀實踐。

注:該圖片來自於Hibernate Validator官網

下面我們以一個具體的列子講述下如何在我們的工程中使用Hibernate Validator

首先我們定義了一個結構體Person,具體的定義如下
public class Person {
@NotNull
private String name;
@Min(value = 1)
private int age;
@NotNull(groups = Intf1.class)
@Size(min = 1, max = 3, groups = Intf2.class)
private String group;
@GenderCase(value = GenderType.FEMALE)
private GenderType gender;
@Max(100)
public int getAge() {
return age;
}
@Max(50)
public int getAgeOther() {
return age + 2;
}
@Max(50)
public int getAgeOther(int num) {
return 51;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

其中該類中用到Max,Min,NotNull,Size等都是JSR 303中內置的約束條件(constraint),GenderCase是自定義的約束條件,這個在後面會介紹。

對Bean進行約束校驗,首先需要先獲得一個校驗器實例
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

(一)如何對JSR 303 內置的約束條件進行校驗
根據上面Person結構體的定義,我們看個簡單的例子
Person person = new Person(null, 20);
Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
assertEquals(1, constraintViolations.size());
System.out.println(constraintViolations);

在Person結構體定義中,name不可以爲null,這裏我們故意構造了一個name的null的Person實例,結果在控制檯輸入的結果如下:

[ConstraintViolationImpl{interpolatedMessage='不能爲null', propertyPath=name, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.NotNull.message}'}]

從上面的例子可以看出,只需要在定義結構體中將JSR 303 內置的約束註解添加到對應的屬性上,通過Validator實例的validate方法,如果返回的Set集合不爲空,通過遍歷集合便可知哪些屬性的值非法。

Bean Validation 中的 constraint
表 1. Bean Validation 中內置的 constraint
@Null 被註釋的元素必須爲 null
@NotNull 被註釋的元素必須不爲 null
@AssertTrue 被註釋的元素必須爲 true
@AssertFalse 被註釋的元素必須爲 false
@Min(value) 被註釋的元素必須是一個數字,其值必須大於等於指定的最小值
@Max(value) 被註釋的元素必須是一個數字,其值必須小於等於指定的最大值
@DecimalMin(value) 被註釋的元素必須是一個數字,其值必須大於等於指定的最小值
@DecimalMax(value) 被註釋的元素必須是一個數字,其值必須小於等於指定的最大值
@Size(max, min) 被註釋的元素的大小必須在指定的範圍內
@Digits (integer, fraction) 被註釋的元素必須是一個數字,其值必須在可接受的範圍內
@Past 被註釋的元素必須是一個過去的日期
@Future 被註釋的元素必須是一個將來的日期
@Pattern(value) 被註釋的元素必須符合指定的正則表達式
表 2. Hibernate Validator 附加的 constraint
@Email 被註釋的元素必須是電子郵箱地址
@Length 被註釋的字符串的大小必須在指定的範圍內
@NotEmpty 被註釋的字符串的必須非空
@Range 被註釋的元素必須在合適的範圍內

注:上面兩個表格中的內容來自於這裏
如果有結構體嵌套,只需要在複合屬性上通過Valid註解,則可以遞歸的進行校驗。

(二)validateValue與validateProperty
通過javax.validation.Validator接口類的定義可知,校驗的方法有三個,分別是validate,validateProperty,validateValue。其中validate會將所有的屬性進行約束校驗,而validateProperty是針對某一個具體的屬性進行校驗,validateValue是對具體的某一個屬性和特定的值進行校驗。具體看下面的兩個例子

第一個例子:

Person person = new Person(null, 101);
Set<ConstraintViolation<Person>> constraintViolations = validator.validateProperty(person, "age");
assertEquals(1, constraintViolations.size());
System.out.println(constraintViolations);

根據上面的結構定義可以看出來在Person結構體中對age的約束有兩個,一個是最小值爲1,另一個是個getter方法上的約束最大不能超過100,執行上面的邏輯輸出的結果爲:

[ConstraintViolationImpl{interpolatedMessage='最大不能超過100', propertyPath=age, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.Max.message}'}]

可見validateProperty不光是對field的值進行校驗,還會對getter方法也進行校驗。

第二個例子:

Set<ConstraintViolation<Person>> constraintViolations = validator.validateValue(Person.class, "name", null);
assertEquals(1, constraintViolations.size());
System.out.println(constraintViolations);

第二個例子表示在執行validateValue時,給定一個結構體定義,field的名稱,看該特定的值是否符合約束,執行的結果如下:

[ConstraintViolationImpl{interpolatedMessage='不能爲null', propertyPath=name, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.NotNull.message}'}]

(三)約束條件的分組
在JSR 303 中定義了group的概念,用定義的接口類來標識,在上面的Person結構體定義的例子中可以看出有個group屬性,該屬性上有兩個約束,分別是@NotNull(groups = Intf1.class) 和@Size(min = 1, max = 3, groups = Intf2.class)

下面通過一段代碼執行的結果來看在參數校驗中如何進行分組

Set<ConstraintViolation<Person>> constraintViolations = validator.validateValue(Person.class, "group", null, Intf1.class);
assertEquals(1, constraintViolations.size());
System.out.println("validate Intf1 |" + constraintViolations);
constraintViolations = validator.validateValue(Person.class, "group",null, Intf2.class);
assertEquals(0, constraintViolations.size());
System.out.println("validate Intf2 |" + constraintViolations);
constraintViolations = validator.validateValue(Person.class, "group","test", Intf2.class, Intf1.class);
assertEquals(1, constraintViolations.size());
System.out.println("validate Intf1&Intf2 |" + constraintViolations);

上段邏輯當group值爲null時,首先用Intf1標識的約束條件進行校驗,在用Intf2標識的約束條件進行校驗。當group值爲test時,同時用Intf1和Intf2標識的約束條件進行校驗。執行的結果如下:

validate Intf1 | [ConstraintViolationImpl{interpolatedMessage='不能爲null', propertyPath=group, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.NotNull.message}'}]
validate Intf2 | []
validate Intf1&Intf2 | [ConstraintViolationImpl{interpolatedMessage='個數必須在1和3之間', propertyPath=group, rootBeanClass=class hibernate.validator.Person, messageTemplate='{javax.validation.constraints.Size.message}'}]

從上面的例子可以看出,可以根據具體的業務選擇不同的校驗規則。

(四)約束條件的定製
對約束條件的定製,需要兩步,第一步是定義約束的註解類:

@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = GenderTypeValidator.class)
public @interface GenderCase {
String message() default "genderType invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
GenderType value() default GenderType.FEMALE;
}

這裏需要關注的有三個點,第一個是@Constraint(validatedBy = GenderTypeValidator.class) 這裏指定了下面將要說的約束校驗的實現類,第二個是message屬性,用於校驗值非法是缺省的消息模版,第三個是註解對應的約束值value。在這個例子中,註解的約束值用的是一個枚舉值表示男/女,缺省值爲女

第二步是實現了javax.validation.ConstraintValidator<A extends Annotation, T>接口的約束校驗實現類,上面說的validatedBy指向的就是該實現類,其中A表示自定義的註解類,T表示進行校驗的字段的類型。具體的邏輯定於如下:

public class GenderTypeValidator implements
ConstraintValidator<GenderCase, GenderType> {
GenderType value;
@Override
public void initialize(GenderCase constraintAnnotation) {
value = constraintAnnotation.value();
}
@Override
public boolean isValid(GenderType obj, ConstraintValidatorContext context) {
if (value != null && obj != null && value != obj) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("gender should be " + value + "| the value is " + obj).addConstraintViolation();
return false;
} else {
return true;
}
}
}

在初始化方法中獲取該註解的約束條件,在isValid方法中將傳進來的obj的值與約束條件比較,如果滿足則返回true表示校驗通過,如果不滿足則返回false,並將錯誤信息存儲上上下文ConstraintValidatorContext中,最終反饋給調用者。

下面是調用該定製約束條件的邏輯:

Set<ConstraintViolation<Person>> constraintViolations = validator.validateValue(Person.class, "gender", GenderType.MALE);
assertEquals(1, constraintViolations.size());
System.out.println(constraintViolations);

執行的結果如下:

[ConstraintViolationImpl{interpolatedMessage='gender should be FEMALE| the value is MALE', propertyPath=gender, rootBeanClass=class hibernate.validator.Person, messageTemplate='gender should be FEMALE| the value is MALE'}]


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