風人園

          弱水三千,只取一瓢,便能解渴;佛法無邊,奉行一法,便能得益。
          隨筆 - 99, 文章 - 181, 評論 - 56, 引用 - 0
          數(shù)據(jù)加載中……

          在Struts 2.0中實現(xiàn)表單數(shù)據(jù)校驗(Validation) (ZT)

          All Input Is Evil!
          -Writing secure code

          在寫前幾篇文章的時候,有些朋友建議我的寫一篇關(guān)于表單數(shù)據(jù)校驗的文章。 正如文章的開頭所引用的《Writing Secure Code》的名言:“所有的輸入都是罪惡的”,所以我們應該對所有的外部輸入進行校驗。而表單是應用程序最簡單的入口,對其傳進來的數(shù)據(jù),我們必須進行校驗。

          轉(zhuǎn)換與校驗(Conversion & Validation)

          其實上篇文章,我本來是打算寫表單數(shù)據(jù)校驗的內(nèi)容,但是經(jīng)過再三思考后,還是決定先寫Struts 2.0轉(zhuǎn)換器的內(nèi)容。原因是我認為轉(zhuǎn)換是校驗的基礎(chǔ),只有在數(shù)據(jù)被正確地轉(zhuǎn)換成其對應的類型后,我們才可以對其取值范圍進行校驗。看個例子相信大家可以更清楚。現(xiàn)在我們就來改造一下《轉(zhuǎn)換器(Converter)——Struts 2.0中的魔術(shù)師》的第一個例子。

          首先,從Action開始,修改后的代碼如下:

          package tutorial;

          import java.util.Locale;

          import com.opensymphony.xwork2.ActionSupport;
          import com.opensymphony.xwork2.util.LocalizedTextUtil;

          public class HelloWorld extends ActionSupport {
             
          private String msg;
             
          private Locale loc = Locale.US;
             
             
          public String getMsg() {
                 
          return msg;        
             }

             
             
          public Locale getLoc() {
                 
          return loc;
             }

             
             
          public void setLoc(Locale loc) {
                 
          this .loc = loc;
             }

             
             @Override
             
          public void validate() {
                 System.out.println(
          " Calling validate() " );
                 
          if ( ! (loc.equals(Locale.US) || loc.equals(Locale.CHINA))) {
                             addFieldError(
          " loc " , getText( " validation.loc " ));
                 }

             }

                 
             
          public void validateExecute() {
                 System.out.println(
          " Calling validateExecute() by reflection " );
             }

             
             @Override
             
          public String execute() {
                 System.out.println(
          " Calling execute() " );
                 
          // LocalizedTextUtil是Struts 2.0中國際化的工具類,<s:text>標志就是通過調(diào)用它實現(xiàn)國際化的
                     msg = LocalizedTextUtil.findDefaultText( " HelloWorld " , loc);
                 
          return SUCCESS;
             }

          }

          然后,修改Struts.xml中Action的定義指明輸入地址:

          < action name ="HelloWorld" class ="tutorial.HelloWorld" >
             
          < result > /HelloWorld.jsp </ result >
             
          < result name ="input" > /HelloWorld.jsp </ result >
          </ action >

          接著,在HelloWorld.jsp中加入錯誤提示:

          <% @ page  contentType = " text/html; charset=UTF-8 " %>
          <% @taglib prefix = " s " uri = " /struts-tags " %>
          < html >
          < head >
             
          < title > Hello World </ title >
          </ head >
          < body >
             
          < div style ="color:red;" >
                 
          < s:fielderror />
             
          </ div >
             
          < s:form action ="HelloWorld" theme ="simple" >            
                  Locale:
          < s:textfield name ="loc" /> &nbsp; < s:submit />
             
          </ s:form >    
             
          < h2 >< s:property value ="msg" /></ h2 >
          </ body >
          </ html >

          再修改LocaleConverter.java文件,將內(nèi)容改為:

          package tutorial;

          import java.util.Locale;
          import java.util.Map;
          import java.util.regex.Pattern;

          public class LocaleConverter extends ognl.DefaultTypeConverter {
             @Override
             
          public Object convertValue(Map context, Object value, Class toType) {
                 
          if (toType == Locale. class ) {            
                     System.out.println(
          " Converting String to Locale " );
                     String locale
          = ((String[]) value)[ 0 ];
                     
          return new Locale(locale.substring( 0 , 2 ), locale.substring( 3 ));
                 }
          else if (toType == String. class ) {
                     System.out.println(
          " Converting Locale to String " );
                     Locale locale
          = (Locale) value;
                     
          return locale.toString();
                 }

                 
          return null ;
             }

          }

          之后,修改國際化資源文件,內(nèi)容為:

          HelloWorld = 你好,世界!
          invalid.fieldvalue.loc
          = Locale必須為\ " xx_XX\ " 的格式
          validation.loc
          = 區(qū)域必須為中國或美國
          globalMessages_zh_CN.properties

          HelloWorld = Hello World!
          invalid.fieldvalue.loc
          = Locale must like \ " xx_XX\ "
          validation.loc
          = Locale must be China or USA
          globalMessages_en_US.properties

          發(fā)布運行應用程序,在瀏覽器中鍵入http://localhost:8080/Struts2_Validation/HelloWorld.action,在Locale中輸入zh_CN,按“Submit”提交,效果如上篇文章所示。而在服務器控制臺有如下輸出:

          Converting String to Locale...
          Calling validateExecute() by reflection...
          Calling validate()...
          Calling execute()...
          Converting Locale to String...

          上述的輸出說明了Struts 2.0的數(shù)據(jù)校驗工作方式,它需要經(jīng)過下面幾個步驟:

          1. 通過轉(zhuǎn)換器將請求參數(shù)轉(zhuǎn)換成相應的Bean屬性;
          2. 判斷轉(zhuǎn)換過程是否出現(xiàn)異常。如果有,則將其保存到ActionContext中,conversionError攔截器再封裝為fieldError;如果沒有,進行下一步;
          3. 通過反射(Reflection)來調(diào)用validateXxx()方法(其中,Xxx表示Action的方法名);
          4. 調(diào)用validate()方法;
          5. 如果經(jīng)過上述步驟沒有出現(xiàn)fieldError,則調(diào)用Action方法;如果有,則會跳過Action方法,通過國際化將fieldError輸出到頁面。

          不喜歡看文字的朋友,可以參考下面的圖1。

          圖1 校驗順序圖
          圖1 校驗順序圖

          看到這里可能大家會疑問:“這么多地方可以校驗表單數(shù)據(jù),到底我應該在那里做呢?”有選擇是好事,但抉擇的過程往往是痛苦的,往往讓人不知所措。如果大家參照以下幾點建議,相信會比較容易地做出正確的抉擇。

          1. 如果需要轉(zhuǎn)換的數(shù)據(jù),通常做法在轉(zhuǎn)換的時候做格式的校驗,在Action中的校驗方法中校驗取值。假如用戶填錯了格式,我們可以通過在資源文件配置invalid.fieldvalue.xxx(xxx為屬性名)來提示用戶正確的格式,不同的階段出錯顯示不同的信息。具體做法請參考上面的例子;
          2. 至于用validate()還是validateXxx(),我推薦使用validate()。原因是validateXxx()使用了反射,相對來說性能稍差,而validate()則是通過接口com.opensymphony.xwork2.Validateable調(diào)用。當然如果你的表單數(shù)據(jù)取值是取決于特定Action方法,則應該使用validateXxx()。

          在運行上面的例子時,在Locale中輸入zh并提交時出現(xiàn)圖2所示頁面。

          圖2 錯誤格式
          圖2 錯誤格式

          在Locale中輸入de_DE時,出現(xiàn)如圖3所示頁面。

          圖3 取值錯誤
          圖3 取值錯誤

          使用Struts 2.0的校驗框架

          上一節(jié)的內(nèi)容都是關(guān)于如何編程實現(xiàn)校驗,這部分工作大都是單調(diào)的重復。更多情況下,我們使用Struts 2.0的校驗框架,通過配置實現(xiàn)一些常見的校驗。

          我學習編程有個習慣——喜歡先看輸出結(jié)果,再看代碼實現(xiàn)。這樣學的好處是先看結(jié)果可以刺激學習的激情,也可以在看代碼前自已思考一下如何實現(xiàn),然后帶著問題去看代碼,那就清晰多了。因此下面我們先來做演示。

          首先,在tutorial包下新建ValidationAction.java,代碼如下:

          package tutorial;

          import com.opensymphony.xwork2.ActionSupport;

          public class ValidationAction extends ActionSupport {
             
          private String reqiuredString;

             
          public String getReqiuredString() {
                 
          return reqiuredString;
             }


             
          public void setReqiuredString(String reqiuredString) {
                 
          this .reqiuredString = reqiuredString;
             }

             
             @Override
             
          public String execute() {
                 
          return SUCCESS;
             }
             
          }

          然后,配置上述所建的Ation,代碼片段如下:

          < action name ="ValidationAction" class ="tutorial.ValidationAction" >
             
          < result > /Output.jsp </ result >
             
          < result name ="input" > /Input.jsp </ result >
          </ action >

          接著,創(chuàng)建Input.jsp和Output.jsp,內(nèi)容分別如下:

          <% @ page  contentType = " text/html; charset=UTF-8 " %>
          <% @taglib prefix = " s " uri = " /struts-tags " %>
          < html >
          < head >
             
          < title > Hello World </ title >
             
          <!-- 此標志的作用是引入Struts 2.0的常用的Javascript和CSS -->
             
          < s:head />
          </ head >
          < body >
             
          < s:form action ="ValidationAction" >            
                 
          < s:textfield name ="reqiuredString" label ="Required String" />
                 
          < s:submit />
             
          </ s:form >    
          </ body >
          </ html >
          Input.jsp

          <% @ page  contentType = " text/html; charset=UTF-8 " %>
          <% @taglib prefix = " s " uri = " /struts-tags " %>
          < html >
          < head >
             
          < title > Hello World </ title >
          </ head >
          < body >
              Required String:
          < s:property value ="reqiuredString" />    
          </ body >
          </ html >
          Output.jsp

          再接下來,在tutorial包下創(chuàng)建ValidationAction的校驗配置文件Xxx-validation.xml(Xxx為Action的類名),在本例中該文件名ValidationAction-validation.xml,內(nèi)容如下:

          <? xml version="1.0" encoding="UTF-8" ?>
          <! DOCTYPE validators PUBLIC 
                    "-//OpenSymphony Group//XWork Validator 1.0//EN" 
                    "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd"
          >          
          < validators >
             
          < field name ="reqiuredString" >
                 
          < field-validator type ="requiredstring" >
                     
          < message > This string is required </ message >
                 
          </ field-validator >
             
          </ field >
          </ validators >

          發(fā)布運行應用程序,在地址欄中鍵入http://localhost:8080/Struts2_Validation/Input.jsp,出現(xiàn)如圖4所示頁面。

          圖4 Input.jsp
          圖4 Input.jsp

          直接點擊“Submit”提交表單,出現(xiàn)圖5所示的頁面。

          圖5 錯誤提示
          圖5 錯誤提示

          在Required String中隨便填點東西,轉(zhuǎn)到Output.jsp頁面,如圖6所示。

          圖6 Output.jsp
          圖6 Output.jsp

          通過上面的例子,大家可以看到使用該校驗框架十分簡單方便。不過,上例還有兩點不足:

          1. 還沒有國際化錯誤消息;
          2. 沒有實現(xiàn)客戶端的校驗。

          當然,要完善以上不足,對于Struts 2.0來說,只是小菜一碟。

          1. 在Xxx-validation.xml文件中的<message>元素中加入key屬性;
          2. 在Input.jsp中的<s:form>標志中加入validate="true"屬性,就可以在用Javascript在客戶端校驗數(shù)據(jù)。

          下面是具體的實現(xiàn),首先在國際化資源文件中加入錯誤消息,然后按照上面說明實現(xiàn)。因為要使用Javascript在客戶端顯示出錯信息,所以在加載Input.jsp頁面時,Struts 2.0需要獲得國際化的字符串,故我們需要使用Action來訪問Input.jsp頁面,具體實現(xiàn)請參考《在Struts 2.0中國際化(i18n)您的應用程序》的最后部分。最后發(fā)布運行應用程序,直接在頁面中點擊“Submit”,表單沒有被提交并出現(xiàn)錯誤提示,如圖7所示:

          圖7 客戶端校驗
          圖7 客戶端校驗

          校驗框架是通過validation攔截器實現(xiàn),該攔載被注冊到默認的攔截器鏈中。它在conversionError攔截器之后,在validateXxx()之前被調(diào)用。這里又出現(xiàn)了一個選擇的問題:到底是應該在action中通過validateXxx()或validate()實現(xiàn)校驗,還是使用validation攔截器?絕大多數(shù)情況,我建議大家使用校驗框架,只有當框架滿足不了您的要求才自已編寫代碼實現(xiàn)。

          配置文件查找順序

          在上面的例子中,我們通過創(chuàng)建ValidationAction-validation.xml來配置表單校驗。Struts 2.0的校驗框架自動會讀取該文件,但這樣就會引出一個問題——如果我的Action繼承其它的Action類,而這兩個Action類都需要對表單數(shù)據(jù)進行校驗,那我是否會在子類的配置文件(Xxx-validation.xml)中復制父類的配置嗎?

          答案是不,因為Struts 2.0的校驗框架跟《在Struts 2.0中國際化(i18n)您的應用程序》提到的“資源文件查找順序”相似,有特定的配置文件查找順序。不同的是校驗框架按照自上而下的順序在類層次查找配置文件。假設(shè)以下條件成立:

          1. 接口 Animal;
          2. 接口 Quadraped 擴展了 Animal;
          3. 類 AnimalImpl 實現(xiàn)了 Animal;
          4. 類 QuadrapedImpl 擴展了 AnimalImpl 實現(xiàn)了 Quadraped;
          5. 類 Dog 擴展了 QuadrapedImpl;

          如果Dog要被校驗,框架方法會查找下面的配置文件(其中別名是Action在struts.xml中定義的別名):

          1. Animal-validation.xml
          2. Animal-別名-validation.xml
          3. AnimalImpl-validation.xml
          4. AnimalImpl-別名-validation.xml
          5. Quadraped-validation.xml
          6. Quadraped-別名-validation.xml
          7. QuadrapedImpl-validation.xml
          8. QuadrapedImpl-別名-validation.xml
          9. Dog-validation.xml
          10. Dog-別名-validation.xml

          已有的校驗器

          Struts 2.0已經(jīng)為您實現(xiàn)很多常用的校驗了,以下在jar的default.xml中的注冊的校驗器。

          < validators >
             
          < validator name ="required" class ="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator" />
             
          < validator name ="requiredstring" class ="com.opensymphony.xwork2.validator.validators.RequiredStringValidator" />
             
          < validator name ="int" class ="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator" />
             
          < validator name ="double" class ="com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator" />
             
          < validator name ="date" class ="com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator" />
             
          < validator name ="expression" class ="com.opensymphony.xwork2.validator.validators.ExpressionValidator" />
             
          < validator name ="fieldexpression" class ="com.opensymphony.xwork2.validator.validators.FieldExpressionValidator" />
             
          < validator name ="email" class ="com.opensymphony.xwork2.validator.validators.EmailValidator" />
             
          < validator name ="url" class ="com.opensymphony.xwork2.validator.validators.URLValidator" />
             
          < validator name ="visitor" class ="com.opensymphony.xwork2.validator.validators.VisitorFieldValidator" />
             
          < validator name ="conversion" class ="com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator" />
             
          < validator name ="stringlength" class ="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator" />
             
          < validator name ="regex" class ="com.opensymphony.xwork2.validator.validators.RegexFieldValidator" />
          </ validators >
          關(guān)于每個校驗器的具體用法,請參考以下鏈接:
          http://wiki.javascud.org/display/ww2cndoc/Validation
          該鏈接中還有一些很有的信息,請大家仔細閱讀。

          總結(jié)

          使用校驗框架既可以方便地實現(xiàn)表單數(shù)據(jù)校驗,又能夠?qū)⑿r炁cAction分離,故我們應該盡可能使用校驗框架。在使用校驗框架時,請不要忘記通過在資源文件加入invalid.fieldvalue.xxx字符串,顯示適合的類型轉(zhuǎn)換出錯信息;或者使用conversion校驗器。

          posted on 2007-05-16 17:14 風人園 閱讀(257) 評論(0)  編輯  收藏 所屬分類: Struts

          主站蜘蛛池模板: 文山县| 太和县| 比如县| 收藏| 毕节市| 金堂县| 甘孜| 徐闻县| 金昌市| 白水县| 盐源县| 弥渡县| 阿合奇县| 法库县| 固阳县| 泉州市| 红桥区| 青海省| 台山市| 铜陵市| 黄骅市| 改则县| 定远县| 前郭尔| 合江县| 葫芦岛市| 比如县| 新建县| 榕江县| 长宁区| 祁阳县| 遵化市| 江都市| 屏山县| 霸州市| 怀宁县| 安远县| 浮梁县| 南陵县| 西峡县| 湖南省|