在項目的業務屬性中,你是不是要經常驗證屬性的取值范圍呢. 想要了解比較優美的解決方案嗎???????????
看看Hibernate Validator 是怎么做的吧.一見到她,相信你就會說: Oh God, 這就是我需要的.
任何獲得Matrix授權的網站,轉載請保留以下作者信息和鏈接:
作者:icess(作者的blog:http://blog.matrix.org.cn/page/icess)
關鍵字:Hibernate Validator
用Annotations 給類或者類的屬性加上約束(constraint),在運行期檢查屬性值是很優雅的.Hibernate Validator就是這樣的一個框架.該框架是十分容易的(就像參考文檔中宣稱的那樣),幾乎沒有什么學習曲線,Validator 是一個驗證框架 不需要和Hibernate的其他部分綁定就可以使用,只要在你的項目中添加Hibernate-annotations.jar庫就可以了.那么下面就讓我們看看怎么使用吧.
Person.java 類
/*
?
*?Created?on?2006-1-12 Person.java
?
*?@author?
?
*/
package?
test.annotation.validator;
import?
org.hibernate.validator.Length;
import?
org.hibernate.validator.Min;
import?
org.hibernate.validator.Valid;
//@Serializability? //測試自定義約束
public?class?
Person?{
??
private?
String?name;
??
private?int?
age;
??
private?
Address?address;
??
??
public?
Person()?{}
??
??
@Valid //注意此處
??
public?
Address?getAddress()?{
????
return?
address;
??
}
??
public?void?
setAddress(Address?address)?{
????
this
.address?=?address;
??
}
??
??
@Min(value?=?
1
)
??
public?int?
getAge()?{
????
return?
age;
??
}
??
public?void?
setAge(
int?
age)?{
????
this
.age?=?age;
??
}
??
??
@Length(min?=?
4
)
??
public?
String?getName()?{
????
return?
name;
??
}
??
public?void?
setName(String?name)?{
????
this
.name?=?name;
??
}
}
Address.java 類
/*
?
*?Created?on?2006-1-12 Address.java
?
*?@author?
?
*/
package?
test.annotation.validator;
import?
org.hibernate.validator.Length;
import?
org.hibernate.validator.Max;
import?
org.hibernate.validator.Min;
public?class?
Address?{
??
private?
String?street;
??
private?int?
num;
??
??
public?
Address()?{}
??
??
@Min(value?=?
1
)
??
@Max(value?=?
100
)
??
public?int?
getNum()?{
????
return?
num;
??
}
??
public?void?
setNum(
int?
num)?{
????
this
.num?=?num;
??
}
??
??
@Length(min?=?
3
,max?=?
8
)
??
public?
String?getStreet()?{
????
return?
street;
??
}
??
public?void?
setStreet(String?street)?{
????
this
.street?=?street;
??
}
}
上面是兩個用 Validator Annotations 注釋的 類. 每個屬性都用 約束限制了.? 下面看看測試的類吧:
TestValidator.java 類
/*
?
*?Created?on?2006-1-12
?
*?@author?icerain
?
*/
package?
test.annotation.validator;
import?
org.hibernate.validator.ClassValidator;
import?
org.hibernate.validator.InvalidValue;
public?class?
TestValidator?{
??
public?void?
test()?{
????
Address?add?=?
new?
Address();
????
add.setNum(
0
);
????
add.setStreet(
"1"
);
????
????
Person?p?=?
new?
Person();
????
p.setAddress(add);
????
p.setAge(
0
);
????
p.setName(
"ice"
);
????
????
/******************Test?validator?********/
???
// 注意該處只驗證了Person 為了說明 @Valid 注釋的使用
????
ClassValidator<Person>?classValidator?=?
new?
ClassValidator<Person>?(Person.
class
);
????
InvalidValue[]?validMessages?=?classValidator.getInvalidValues(p);
????
for?
(InvalidValue?value?:?validMessages)?{
??????
????
System.out.println(
"InvalidValue?的長度是:"?
+?validMessages.length
????????
+
"?.?驗證消息是:?"?
+?value.getMessage()?
????????
+?
"?.?PropertyPath?是:"?
+?value.getPropertyPath()
????????
+
"?.\n\t?PropertyName?是:?"?
+value.getPropertyName()
????????
+?
"Value?是:?"?
+?value.getValue()
????????
+
"?Bean?是:?"
+?value.getBean()
????????
+
"\n\t?BeanClass?是:"?
+?value.getBeanClass());
????
}
??
}
??
??
public?static?void?
main(String[]?args)?{
????
new?
TestValidator().test();
??
}
}
程序的輸出如下 InvalidValue 的長度是:4 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:age .
PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@dd87b2
BeanClass 是:class test.annotation.validator.Person
InvalidValue 的長度是:4 . 驗證消息是: 長度必須介于 4 與 2147483647 之間 . PropertyPath 是:name .
PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@dd87b2
BeanClass 是:class test.annotation.validator.Person
InvalidValue 的長度是:4 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:address.num .
PropertyName 是: num. Value 是: 0 Bean 是: test.annotation.validator.Address@197d257
BeanClass 是:class test.annotation.validator.Address
InvalidValue 的長度是:4 . 驗證消息是: 長度必須介于 3 與 8 之間 . PropertyPath 是:address.street .
PropertyName 是: street. Value 是: 1 Bean 是: test.annotation.validator.Address@197d257
BeanClass 是:class test.annotation.validator.Address
可以看出不滿足約束的值都被指出了.
同時該句: ClassValidator<Person>?classValidator?=?new?ClassValidator<Person>?(Person.class);
我們只驗證了 Person. 在Person里面的Address的屬性 由于有@Valid Annotations 所以 Address的相關屬性也被機聯驗證了 .
如果 把
@Valid Annotations 去掉,結果如下:
InvalidValue 的長度是:2 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:age .
PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@18fef3d
BeanClass 是:class test.annotation.validator.Person
InvalidValue 的長度是:2 . 驗證消息是: 長度必須介于 4 與 2147483647 之間 . PropertyPath 是:name .
PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@18fef3d
BeanClass 是:class test.annotation.validator.Person
可以看出 沒有驗證 Address.
當然了 ,你還可以只驗證一個屬性 , 沒有必要驗證整個類.只需要在調用
classValidator.getInvalidValues(p,"age")方法時 加上你要驗證的屬性就可以了.如我們只想驗證age 屬性 把代碼改為如下所示:
InvalidValue[] validMessages = classValidator.getInvalidValues(p,"age"); /
/只驗證age 屬性
運行結果如下:
InvalidValue 的長度是:1 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:age .
PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@1457cb
BeanClass 是:class test.annotation.validator.Person
只是驗證了 age 屬性.
怎么樣 ,很簡單吧. 關于 Hibernate Validator 內建的驗證Annotations 大家可以看看 API 或者 參考文檔(中文版我正在翻譯中 請訪問我的 Blog 獲得最新信息).
如果你要寫自己的約束呢 , 你不用擔心 ,這也是很容易的.
任何約束有兩部分組成: [約束描述符 即注釋]the constraint descriptor (the annotation) 和[約束validator 即 實現類] the constraint validator (the implementation class).下面我們擴展Hibernate Test suit 中的一個Test 來講解一下.
首先: 要聲明一個
constraint descriptor .如下:
package?
test.annotation.validator;
import?
java.lang.annotation.Documented;
import?static?
java.lang.annotation.ElementType.TYPE;
import?static?
java.lang.annotation.ElementType.FIELD;
import?static?
java.lang.annotation.ElementType.METHOD;
import?
java.lang.annotation.Retention;
import?static?
java.lang.annotation.RetentionPolicy.RUNTIME;
import?
java.lang.annotation.Target;
import?
org.hibernate.validator.ValidatorClass;
/**
?
*?Dummy?sample?of?a?bean-level?validation?annotation
?
*
?
*?
@author?
Emmanuel?Bernard
?
*/
@ValidatorClass(SerializabilityValidator.
class
)
@Target({METHOD,FIELD,TYPE})
@Retention(RUNTIME)
@Documented
public?
@interface?Serializability?{
??
int?
num()?
default?
11
;
??
String?message()?
default?
"bean?must?be?serialiable"
;
}
@ValidatorClass(SerializabilityValidator.
class
) 指出了
constraint validator 類.
@Target({METHOD,FIELD,TYPE})
@Retention(RUNTIME)
@Documented????????????????
這幾個我就不用解釋了吧.
Serializability?里面聲明了一個 message 顯示約束的提示信息. num 只是為了說明一個方面 在這里面沒有實際用途用 .
然后就是 實現一個
constraint validator 類 該類要實現Validator<ConstraintAnnotation>.這里是SerializabilityValidator.java 如下:
//$Id:?SerializabilityValidator.java,v?1.3?2005/11/17?18:12:11?epbernard?Exp?$
package?
test.annotation.validator;
import?
java.io.Serializable;
import?
org.hibernate.validator.Validator;
/**
?
*?Sample?of?a?bean-level?validator
?
*
?
*?
@author?
Emmanuel?Bernard
?
*/
public?class?
SerializabilityValidator?
implements?
Validator<Serializability>,?Serializable?{
??
public?boolean?
isValid(Object?value)?{
???
//這里只是Validator 里面的 實現驗證規則的 方法. value 是要驗證的值.
????
System.out.println(
"IN?SerializabilityValidator?isValid:"
+value.getClass()+
":?"?
+value.toString());
????
return?
value?instanceof?Serializable;
??}
??public?void?initialize(Serializability?parameters)?{
????//?在這里可以 取得
constraint descriptor 里面的屬性 如上面我們聲明的 num
????
System.out.println(
"IN?SerializabilityValidator:?parameters:"
+?parameters.num()?);
??
}
}
然后在你的類中應用@Serializability? 就可以約束一個類實現
Serializable 接口了. 如下:
在我們的Person.java類 添加@Serializability? Annotations ,把Person.java 中的
//@Serializability //測試自定義約束 注釋去掉就ok了.
運行結果如下:
InvalidValue 的長度是:3 . 驗證消息是:
bean must be serialiable
. PropertyPath 是:null .
PropertyName 是: null. Value 是: test.annotation.validator.Person@1a73d3c Bean 是: test.annotation.validator.Person@1a73d3c
BeanClass 是:class test.annotation.validator.Person
現在把Person類實現 java.io.Serializable 接口 則沒有出現 驗證錯誤消息.
消息的國際化也是很簡單的,把
Serializability? 中的message 改為以{}擴住的 屬性文件的Key就可以了
public?
@interface?Serializability?{
??
int?
num()?
default?
11
;
??
String?message()?
default?
"{Serializable}";
//"bean?must?be?serialiable";
//消息的國際化
}
然后編輯資料文件. 注意 該資源文件中要包括 Hibernate Validator 內建的資源. 可以在該org\hibernate\validator\resources 包里面的資源文件基礎上修改 ,在打包里面 這樣就可以了. 自己打包可能不太方便.你可以把該包里面的文件復制出來.然后放到你自己的項目包下在自己編輯, 該測試中 我是放在 test\resources 包下的.
然后在 資源文件中添加
Serializable = '''''' 這么一行, 樣例如下:
#DefaultValidatorMessages.properties (DefaultValidatorMessages_zh.properties 不再列出^_^)
#下面是 Hibernate Validator 內建的國際化消息
validator.assertFalse=
assertion
failed
validator.assertTrue=
assertion
failed
validator.future=
must
be
a
future
date
validator.length=
length
must
be
between
{min}
and
{max}
validator.max=
must
be
less
than
or
equal
to
{value}
validator.min=
must
be
greater
than
or
equal
to
{value}
validator.notNull=
may
not
be
null
validator.past=
must
be
a
past
date
validator.pattern=
must
match
"{regex}"
validator.range=
must
be
between
{min}
and
{max}
validator.size=
size
must
be
between
{min}
and
{max}
#下面是自定義的消息
Serializable=
Bean
not
Serializable? //加上自己定義的國際化消息.
在構造
ClassValidator
時要添上 資源文件 如下:(在測試類中)
ClassValidator<Person> classValidator = new ClassValidator<Person> (Person.class,ResourceBundle.getBundle("test.resources.DefaultValidatorMessages"));//加載資源
這樣就可以了 .? 當然 你還可以 更改 Hibernate Validator 的消息(不是在上面的資源文件中直接修改
validator.length = ... 等等
) , 還記得 Validator 注釋中有個 message 元素嗎? 你以前用的都是默認值,現在你可以該為你自己定義的了.
如:validator.length 我把他改為 "該字符串的長度不符合規定范圍范圍". 在資源文件中添加一行鍵值屬性對(key定義為 "myMsg")如下:
myMsg=該字符串的長度不符合規定范圍范圍
并且還要在
@Length
注釋中提供message的引用的key 如下
@Length(min = 4,message = "{
myMsg
}")
再一次運行測試 ,我們就可以看到上面兩條自定義綁定的消息了 .如下:
InvalidValue 的長度是:3 . 驗證消息是: Bean 不是 可 Serializable . PropertyPath 是:null .
PropertyName 是: null. Value 是: test.annotation.validator.Person@1bd4722 Bean 是: test.annotation.validator.Person@1bd4722
BeanClass 是:class test.annotation.validator.Person
InvalidValue 的長度是:3 . 驗證消息是: 該字符串的長度不符合規定范圍范圍 . PropertyPath 是:name .
PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@1bd4722
BeanClass 是:class test.annotation.validator.Person
怎么樣,比你想象的簡單吧.
OK 上面我們討論了
Hibernate Validator 的主要用法: 但是 該框架有什么用呢? ^_^
看到這里其實不用我在多說了 大家都知道怎么用,什么時候用. 作為一篇介紹性文章我還是在此給出一個最常用的例子吧,更好的使用方式大家慢慢挖掘吧.
比如 : 你現在在開發一個人力資源(HR)系統 (其實是我們ERP課程的一個作業 ^_^), 里面要處理大量的數據,尤其是在輸入各種資料時 如 登記員工信息. 如果你公司的員工的年齡要求是18 -- 60 那么你所輸入的年齡就不能超出這個范圍. 你可能會說這很容易啊 , 不用Validator就可以解決啊.這保持數據前驗證就可以啦 如if ( e.getAge() > 60 || e.getAge() < 18 ) ........ 給出錯誤信息 然后提示重新輸入不就OK啦 用得著 興師動眾的來個第三方框架嗎?
是啊 當就驗證這一個屬性時, 沒有必要啊 ! 但是一個真正的HR 系統,會只有一個屬性要驗證嗎? 恐怕要有N多吧
你要是每一個都那樣 寫一段驗證代碼 是不是很煩啊 ,況且也不方便代碼重用. 現在考慮一些 Validator 是不是更高效啊,攔截到 約束違例的 屬性 就可以直接得到 國際化的消息 可以把該消息顯示到一個彈出對話框上 提示更正? !
Validator的用處不只這一種 ,你可以想到如何用呢 ! 歡迎發表你的高見!!
OK 到此 我們的 Hibernate Validator 之旅就要先告一段落了 . 希望這是令你心曠神怡的一次寒冬之旅,
把你學到的應用到你的項目中吧,一定會提高你的生產率的. 相信我 ,沒錯的? ^_^ !