本文較詳盡地介紹了jakarta開源項目的子項目之一commons-validator(通用驗證系統),版本是 1.0.2。它使用了一個xml文件來定義針對用戶輸入的數據驗證功能,整個驗證體系提供了很強的擴展性,使得開發者可以開發自己的驗證函數加入到這個驗 證體系中來。它對web應用程序提供了客戶端javascript驗證和服務端驗證的兩種選擇,但是它只是一個驗證體系,有些東西還需要自己開發特別是 validatoraction的開發,不過有了項目源代碼及其例子,還有struts這個優秀的開源項目的示范,使用好commons- validator驗證體系應該是挺容易的。本文就這個驗證體系作了些探討,希望對大家有用!
本文較詳盡地介紹了jakarta開源項目的子項目之一commons-validator(通用驗證系統),版本是1.0.2。它使用了一個xml文件 來定義針對用戶輸入的數據驗證功能,整個驗證體系提供了很強的擴展性,使得開發者可以開發自己的驗證函數加入到這個驗證體系中來。它對web應用程序提供 了客戶端javascript驗證和服務端驗證的兩種選擇,但是它只是一個驗證體系,有些東西還需要自己開發特別是validatoraction的開 發,不過有了項目源代碼及其例子,還有struts這個優秀的開源項目的示范,使用好commons-validator驗證體系應該是挺容易的。本文就 這個驗證體系作了些探討,希望對大家有用!
?
![]() ![]() |
![]()
|
我 們在開發信息系統時,用戶界面往往是一個很容易忽視的但是確是相當重要的地方。我們有好多關于編寫后端代碼的設計模式,現在我們還擁有commons- validator這樣的優秀驗證體系對付用戶界面的用戶千變萬化的輸入可能。輸入驗證關乎到整個信息系統的強壯性,因為惡意的輸入數據可能導致信息系統 崩潰;輸入驗證還關乎到信息系統的友好性,因為不能給用戶提供正確的輸入導引經常搞得使用者手足無措,最后只有悲憤而去。
?
![]() ![]() |
![]()
|
通過對上面用戶問題的描述,我們可以簡單分析一下驗證體系的基本特性:
- 驗證體系應該具有良好的可擴展性,可以讓信息系統開發者開發自己的驗證功能,以滿足特殊系統的驗證要求。
- 驗證體系應該能顯示準確的驗證錯誤信息,用以幫助使用者糾正錯誤,而且錯誤信息應該是外在可配置的,改變相應的錯誤信息不需要修改源代碼。
- 對于web信息系統來說,應該能支持客戶端驗證和服務端驗證兩種方式。
?
![]() ![]() |
![]()
|
下面是驗證規則xml文件的元素關系圖,我將挑選一些重要而又相對復雜的元素進行講解。
1. 元素constant
"constant" 元素定義了"field"元素所使用的替換型參數的靜態值。 "constant-name" 和 "constant-value" 元素分別表示這個靜態值的引用標識和值
2. 元素validator
這個"validator"元素定義了formset元素字段所能使用的 validatoraction對象。
?
子元素 | javascript | ||||||||||||||||||||||
屬性 |
|
3. 元素formset
"formset" 定義了一個針對locale的 form集. "form"元素定義了有待驗證的"field" 集,名字屬性是應用程序分配給這個"form"的引用標識。
?
子元素 | constant form | ||||||||||||
屬性 |
|
4. 元素field
"field" 元素定義了需要驗證的屬性,在web應用中,一個"field"對應于一個HTML 表單控件。驗證系統通過驗證一個JavaBean來驗證這個"field" 元素,這個元素可以接受4個屬性:
?
子元素 | msg arg0 arg1 arg2 arg3 var | |||||||||||||||
屬性 |
|
5. 元素msg
"msg" 元素定義了一個定制消息鍵,用來為驗證失敗的"field"提供消息文本。 當"field"沒有子元素"msg" 元素時,每個validatoraction對象則使用自己的消息屬性。
?
屬性 |
|
6. 元素arg0|arg1|arg2|arg3
這 是4個參數元素,定義了validator 或field 消息模版中的4個替換值。比如validator的msg對應的消息資源是"必須提供{0}字段,而且字段的長度不能小于{1}字符! ",在顯示錯誤的時候,其中{0}將被arg0的消息文本替換,而{1}將被arg1的消息文本替換。
?
屬性 |
|
7. 元素var
"field"能通過這個元素向某個validatoraction對象傳遞參數,這些參數也能被arg?元素通過語法${var:var-name}引用。它的子元素var-name和var-value分別為變量標識和變量的值。
如 圖《Commons-validator的API》所示,commons-validator的類明顯的分成三種,第一種為代表驗證規則文件中各個元素的 類,本文稱元素類,第二種是程序準備驗證資料和驗證的類,本文稱fa?ade類,第三種是實現了通用功能的類,本文稱工具類。元素類代表了驗證規則文件中 的各個元素,對于編程者來說主要作用是用他們來得到消息文本;fa?ade類用來使Commons-validator驗證系統融入到應用系統中;而工具 類有助于編程者寫實現各種validatorAction的類。具體的使用參見下面的代碼樣例。
雖然common-validation是為web應用寫的驗證體系,它同時也能用在java應用程序中,為了把注意力放在驗證系統的介紹上,下面的驗證樣例使用java應用程序來表演。
驗 證規則是一個xml文件,定義了需要驗證的表單,及其表單的各個字段以及字段的驗證要求,另外validator元素是用來完成各個字段的驗證要求的。本 例定義了一個輸入表單nameForm及其兩個字段,兩個字段都必須提供,而且age字段還必須是整數;還定義了兩個驗證動作int和required, 分別滿足整數要求和必須提供的要求:
<form-validation> <global> <validator name="int" classname="org.i505.validator.MyTypeValidator" method="validateInt" methodParams="java.lang.Object,org.apache.commons.validator.Field" msg="errors.int"/> <validator name="required" classname="org.i505.validator.MyValidator" method="validateRequired" methodParams="java.lang.Object,org.apache.commons.validator.Field" <!----> msg="errors.required"/> </global> <formset> <form name="nameForm"> <field property="username" depends="required"> <arg0 key="nameForm.username.displayname"/> </field> <field property="age" depends="required,int"> <arg0 key="nameForm.age.displayname"/> </field> </form> </formset> </form-validation> |
?
4.3.2. 編寫消息資源文件
commons-validator的消息資源包括兩大部份,第一部分是包括了參數占位符的validatoraction對象的消息,第二部分是各個輸入表單輸入數據的顯示信息,用作驗證失敗時的信息顯示。本例中值包括了一個輸入表單的顯示信息:
# validatoraction對象的消息 errors.required=必須提供{0}字段! errors.int= {0}字段必須是整數! # nameForm輸入表單的各個輸入數據的顯示信息 nameForm.username.displayname=姓名 nameForm.age.displayname=年齡 |
?
4.3.3. 編寫validatorAction
我們從驗證定義規則文件中可以看出validator元素定義的int和required validatorAction分別使用了org.i505.validator.MyTypeValidator和 org.i505.validator.MyValidator兩個類,這個元素還定義了它們使用的驗證方法validateInt和 validateRequired以及方法的參數類型列表。下面是這兩個類的代碼:
package org.i505.validator;import org.apache.commons.validator.Field; import org.apache.commons.validator.GenericTypeValidator; import org.apache.commons.validator.ValidatorUtil; public class MyTypeValidator { public static Integer validateInt(Object bean, Field field) { String value = ValidatorUtil.getValueAsString(bean, field.getProperty()); Integer x= GenericTypeValidator.formatInt(value); return x; } } |
package org.i505.validator; import org.apache.commons.validator.Field; import org.apache.commons.validator.GenericValidator; import org.apache.commons.validator.ValidatorUtil; public class MyValidator { public static boolean validateRequired(Object bean, Field field) { String value = ValidatorUtil.getValueAsString(bean, field.getProperty()); return !GenericValidator.isBlankOrNull(value); } } |
?
4.3.4. 編寫javabean
commons-validator是一個針對web應用的輸入驗證體系,驗證規則中的form定義是針對html form表單的,但是common-validator在內部驗證時需要javabean。這個javabean的各個屬性就代表了html form表單的輸入控制。所以針對前面的驗證規則,我們實現的javabean需要定義兩個屬性:age和username,代碼如下:
public class ValidateBean extends Object { String username;String age; public void setUsername (String username) { this. username = username; } public String getUsername () { return this.username; } public void setAge (String age) { this.age = age; } public String getAge () { return this.age; } public String toString() { return "{ username =" + this.username + ", age=" + this.age + "}"; } } |
?
注意,這個驗證BEAN的age屬性的類型是字符串 型的,因為它只是代表了html form表單的輸入控制的值,原始的用戶輸入數據基本上都可以用String來表示,如果我們申明age屬性的類型時整數型,則我們在html form表單的值到BEAN的age屬性就經過了一次類型轉換,這個早于我們的整型驗證,所以可能有產生類型轉換錯誤的危險。
4.3.5. 編寫驗證主程序
編寫驗證主程序主要有下面五步:
- 創建和處理ValidatorResources對象,這要借助于ValidatorResourcesInitializer類利用驗證規則定義文件初始化這個對象。
- 創建要驗證的bean對象
- 用驗證規則定義文件中定義的某個form創建validator對象,并且告訴這個對象要驗證的bean對象。
- 運行validator對象的validate()方法實際驗證bean對象
- 打印驗證結果
下面是依據上面所述步驟編寫的實例代碼,代碼中進行了三次驗證,第一次是驗證兩個屬性都是空的bean對象,第二次是age屬性不合法的bean對象,第三次是兩個屬性都合法的bean對象:
public static void main(String[] args) throws IOException, ValidatorException { InputStream in = null; try { ValidatorResources resources = new ValidatorResources(); in = ValidateExample.class.getResourceAsStream("myvalidator-example.xml"); <!----> ValidatorResourcesInitializer.initialize(resources, in); ValidateBean bean = new ValidateBean(); Validator validator = new Validator(resources, "nameForm"); validator.addResource(Validator.BEAN_KEY, bean); ValidatorResults results = null; results = validator.validate(); printResults(bean, results, resources); bean.setUsername("龔永生"); bean.setAge("很年輕"); results = validator.validate(); printResults(bean, results, resources); bean.setAge("28"); results = validator.validate(); printResults(bean, results, resources); } finally { if (in != null) { in.close(); } } } |
?
4.3.6. 打印驗證結果
打 印驗證結果可能是驗證體系中最復雜的一部分,因為它涉及到驗證文件和消息資源文件,涉及到好多對象以及它們復雜的關系。特別需要指出的是錯誤消息文本的顯 示。下面的代碼包括三個部分:第一部分是使用資源文件生成ResourceBundle對象,注意你的資源文件必須在classloader能找到的地 方;第二部分是實際打印驗證結果;第三部分是個顯示中文消息的函數。
validator對象的validate()方法會把驗證結果保存到其返回的ValidatorResults對象 中,它保存了bean對象被驗證的每個屬性的各種驗證要求的驗證結果對象ValidatorResult,首先我們可以獲取bean對象對應的驗證文件定 義的form,從而得到相應的消息鍵和其它信息,而且通過這些信息從ValidatorResults對象中獲取相應的ValidatorResult對 象,利用ValidatorResult對象isValid函數可以判斷驗證的成功與否,如果驗證沒通過,可以使用form的信息顯示錯誤消息文本。
private static ResourceBundle apps = ResourceBundle.getBundle( "org.i505.validator.myapplicationResources"); public static void printResults( ValidateBean bean, ValidatorResults results, ValidatorResources resources) { boolean success = true; Form form = resources.get(Locale.getDefault(), "nameForm"); <!----> System.out.println("\n\n驗證:"); System.out.println(bean); Iterator propertyNames = results.get(); while (propertyNames.hasNext()) { String propertyName = (String) propertyNames.next(); <!----> Field field = (Field) form.getFieldMap().get(propertyName); <!----> String prettyFieldName = getGBKMsg(apps.getString(field.getArg0().getKey())); <!----> ValidatorResult result = results.getValidatorResult(propertyName); <!----> Map actionMap = result.getActionMap(); Iterator keys = actionMap.keySet().iterator(); while (keys.hasNext()) { String actName = (String) keys.next(); <!----> ValidatorAction action = resources.getValidatorAction(actName); <!----> System.out.println( propertyName + "[" + actName + "] (" + (result.isValid(actName) ? "驗證通過" : "驗證失敗") <!----> + ")"); if (!result.isValid(actName)) { success = false; String message = getGBKMsg(apps.getString(action.getMsg())); <!----> Object[] args = { prettyFieldName }; <!----> System.out.println( "錯誤信息是: " + MessageFormat.format(message, args)); <!----> } } } if (success) { System.out.println("表單驗證通過"); } else { System.out.println("表單驗證失敗"); } } public static String getGBKMsg(String msg){ String gbkStr=""; try { gbkStr=new String(msg.getBytes("iso-8859-1"),"gbk"); <!----> } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block <!----> e.printStackTrace(); } return gbkStr; } |
?
驗證結果如下:
驗證:{ username =null, age=null} age[required] (驗證失敗)錯誤信息是: 必須提供年齡字段! username[required] (驗證失敗)錯誤信息是: 必須提供姓名字段! 表單驗證失敗 驗證:{ username =龔永生, age=很年輕} age[required] (驗證通過) age[int] (驗證失敗) 錯誤信息是: 年齡字段必須是整數! username[required] (驗證通過)表單驗證失敗 驗證:{ username =龔永生, age=28} age[required] (驗證通過) age[int] (驗證通過) username[required] (驗證通過) 表單驗證通過 |
![]() ![]() |
![]()
|
ValidatorResults對象有個map,以field的getKey()為鍵,這個field的驗證結果ValidatorResult對象為值。
ValidatorResult對象也有個map,以field的各個validator元素的名字為鍵(在field元素的depends中定一個field的validator元素列表),以一個表示驗證成功與否的對象為值。
ValidatorResources對象包含一個map,以Locale的某種字符串表示為鍵,FormSet 為值(所以formset有多種版本),還包含一個map,保存了全局常量,以常量名為鍵,常量值為值;還包含一個map,以validator元素的 name屬性為鍵, validatorAction對象為值。
Formset對象包含一個map,以form的name屬性為鍵,Form對象為值;還包含一個map,以formset元素的子元素Constant的name為鍵,子元素Constant的值為值。
Form對象包含一個map,以Field元素對應的Field對象的getKey()為鍵,Field對象為值;另外還擁有一個保存順序的field對象數組。
field對象擁有一個map,以var的名字為鍵,var對象為值。
Validator對象包含一個map,以各個validator元素的methodParams參數列表中的名字為鍵,相應的對象為值,這個map的鍵和值將會用作調用相應validator元素中的methods屬性指定方法的參數。
通過這些map,commons-validator在驗證系統各個類間鋪了一張類關系表,見下圖:
驗 證規則的validator元素定義了validatorAction,而field元素則通過depends屬性引用了這些 validatorAction。從上面代碼樣例中的驗證主程序可以知道validator.validate()方法是針對某個form元素的,它將對 這個form元素的各個field進行驗證,對field進行驗證也就是調用field元素的depends屬性引用的各個validator元素定義的 驗證方法。
validator元素使用classname、method和methodParams三個屬性定義了一個驗證方法, 比如下面的xml片斷就定義了一個驗證整數的驗證方法validateInt,這個方法帶有兩個參數,類型依次是 java.lang.Object,org.apache.commons.validator.Field。驗證方法validateInt將在 org.i505.validator.MyTypeValidator代碼中實現。
<validator name="int" classname="org.i505.validator.MyTypeValidator" method="validateInt" methodParams="java.lang.Object,org.apache.commons.validator.Field" msg="errors.int"/> |
?
講了這么多,現在的問題是validator.validate()方法是如何調用各個驗證方法(比如validateInt)的?
我們用一個順序圖和一段代碼剖析這個問題。
上圖是個簡要的順序圖,這個順序圖的解釋圖下:
1. 向validator對象增加資源(向資源map增加項)
2. 實際驗證
對form定義的每個field,調用如下步驟:
#begin
3. 驗證一個field
對field的每個validatoraction,執行如下步驟:
#begin
4. 驗證一個validatoraction
5. 合并驗證結果
#end
#end
下面代碼詳細解釋了上面的第四步:驗證一個validatoraction。
// Add these two Objects to the resources since they reference // the current validator action and field hResources.put(VALIDATOR_ACTION_KEY, va); hResources.put(FIELD_KEY, field); Class c = getClassLoader().loadClass(va.getClassname()); List lParams = va.getMethodParamsList(); int size = lParams.size(); int beanIndexPos = -1; int fieldIndexPos = -1; Class[] paramClass = new Class[size]; Object[] paramValue = new Object[size]; for (int x = 0; x < size; x++) { String paramKey = (String) lParams.get(x); if (BEAN_KEY.equals(paramKey)) { beanIndexPos = x; } if (FIELD_KEY.equals(paramKey)) { fieldIndexPos = x; } // There were problems calling getClass on paramValue[] paramClass[x] = getClassLoader().loadClass(paramKey); paramValue[x] = hResources.get(paramKey); } Method m = c.getMethod(va.getMethod(), paramClass); // If the method is static we don't need an instance of the class // to call the method. If it isn't, we do. if (!Modifier.isStatic(m.getModifiers())) { try { if (va.getClassnameInstance() == null) { va.setClassnameInstance(c.newInstance()); } } catch (Exception ex) { log.error( "Couldn't load instance " + "of class " + va.getClassname() + ". " + ex.getMessage()); } } Object result = null; if (field.isIndexed()) { Object oIndexed = PropertyUtils.getProperty( hResources.get(BEAN_KEY), field.getIndexedListProperty()); Object indexedList[] = new Object[0]; if (oIndexed instanceof Collection) { indexedList = ((Collection) oIndexed).toArray(); <!----> } else if (oIndexed.getClass().isArray()) { indexedList = (Object[]) oIndexed; } // Set current iteration object to the parameter array <!----> paramValue[beanIndexPos] = indexedList[pos]; // Set field clone with the key modified to represent // the current field Field indexedField = (Field) field.clone(); indexedField.setKey( ValidatorUtil.replace( indexedField.getKey(), Field.TOKEN_INDEXED, "[" + pos + "]")); paramValue[fieldIndexPos] = indexedField; result = m.invoke(va.getClassnameInstance(), paramValue); <!----> results.add(field, va.getName(), isValid(result), result); <!----> if (!isValid(result)) { return false; } } else { result = m.invoke(va.getClassnameInstance(), paramValue); <!----> results.add(field, va.getName(), isValid(result), result); <!----> if (!isValid(result)) { return false; } } |
?
這段代碼首先增加了兩個資源:目前正在驗證的field和validatoraction,接著實例化驗證方法所在類的一個對象,接著按照資源map的鍵/值和驗證方法的參數類列表構造驗證方法的參數列表,最后調用驗證方法所在類的一個對象的驗證方法。
?
![]() ![]() |
![]()
|
我 們說commons-validator是個通用的驗證系統,它確實是個不錯的東西,但是要想在實際系統中使用它還需要一定的工作,特別是想利用它的客戶 端驗證時尤為如此。所幸的是struts項目為我們使用這些這個驗證系統作了很經典的示范,本人認為有必要把struts項目的這些工作移到 commons-validator項目中來,這樣它的可用性將大大提高。
?
![]() ![]() |
![]()
|
作 為一個驗證的通用框架,有些功能不是立即可用的,它需要開發者再次包裝。Struts就重新包裝了commons-validator的客戶端驗證機制, 使得這種機制在開發struts程序來說是立即可用的。有了這些包裝,剩下的任務就是開發validatoraction來滿足不同的驗證要求了。另外 struts還提供了驗證和某個正則表達式匹配的輸入,它使用了commons-validator的perl5正則表達式匹配機制。
在開發web信息系統時,除了驗證輸入外,我們還需要注意數據的輸出。Web的界面是html代碼,而且這個代碼是由瀏 覽器來解釋的,如果我們的內部數據包括了html代碼的保留字,輕一點危害是破壞瀏覽器對html的解釋,搞壞了我們的最后界面;重一點的是引入安全隱 患,癱瘓信息系統。下面這段代碼可用于過濾html保留字,學著URLEncoding的樣,我把它稱為HTMLEncoding:
public static String HTMLEncoding (String value) { if (value == null) return (null); char content[] = new char[value.length()]; value.getChars(0, value.length(), content, 0); StringBuffer result = new StringBuffer(content.length + 50); for (int i = 0; i < content.length; i++) { switch (content[i]) { case '<': result.append("<"); break; case '>': result.append(">"); break; case '&': result.append("&"); break; case '"': result.append("""); break; case '\'': result.append("'"); break; 注釋與缺省值: result.append(content[i]); } } return (result.toString()); }
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
