7.2 校验
校验(validation)表单对于防止不正确的数据进入应用程序是必不可少的。最好是尽可能捕获用户输入数据的问题,告诉用户发生了什么错误,以及如何来修整它,这是一个用户友好系统的检验尺度。
如果用户输入的数据是不能正确格式化并且不能正确转到此属性的格式,这个问题会被在7.2.2节中介绍的类型转换框架捕获。
7.2.1 手动校验
最直接的校验表单数据的方式就是在action里编写校验代码。这个方法的问题是限制了复用,因为校验被限制在action类里,不容易在action之间或者应用程序的其他部分复用。但是在一些情况下,一些商业逻辑可能必须也只能在action类中实现,也就是说手动校验可能是最后的选择。
在5.5节已经介绍了如何利用Validateable接口来实现校验代码与execute()函数分离,以及利用ValidationAware来处理错误信息。如果必须使用手动校验应该采用这种方法,而不要把校验代码直接加入到execute()方法中去。本节重点介绍如何利用Struts2的框架来进行校验。
7.2.2 使用框架校验
校验框架自动读取validation文件里面的定义进行校验。这些文件和action类放在相同的包里,命名为ClassName-validation.xml。如实例7-9所示是一个SimpleAction-validation.xml的例子。
【实例7-9】框架校验:SimpleAction-validation.xml
01 <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" 02 "http://sw.opensymphony.com/xwork/xwork-validator-1.0.2.dtd"> 03 <validators> 04 <!--字段校验器--> 05 <field name="bar"> 06 <field-validator type="required"> 07 <message>You must enter a value for bar.</message> 08 </field-validator> 09 <field-validator type="int"> 10 <param name="min">6</param> 11 <param name="max">10</param> 12 <message> 13 bar must be between ${min} and ${max}, current value is ${bar}。 14 </message> 15 </field-validator> 16 </field> 17 <!--字段校验器--> 18 <field name="bar2"> 19 <field-validator type="regex"> 20 <param name="regex">[0-9], [0-9]</param> 21 <message> 2 2 The value of bar2 must be in the format "x, y", where x and y are between 0 23 and 9 24 </message> 25 </field-validator> 26 </field> 27 <!--字段校验器--> 28 <field name="date"> 29 <field-validator type="date"> 30 <param name="min">12/22/2002</param> 31 <param name="max">12/25/2002</param> 32 <message> 33 The date must be between 12-22-2002 and 12-25-2002 34 </message> 35 </field-validator> 36 </field> 37 <!--字段校验器--> 38 <field name="foo"> 39 <field-validator type="int"> 40 <param name="min">0</param> 41 <param name="max">100</param> 42 <message key="foo.range">Could not find foo.range!</message> 43 </field-validator> 44 </field> 45 <!--表达式校验器--> 46 <validator type="expression"> 47 <param name="expression"> foo > bar</param> 48 <message> 49 Foo must be greater than Bar. Foo = ${foo}, Bar = ${bar}. 50 </message> 51 </validator> 52 </validators>
【代码剖析】从上述代码可以看到SimpleAction类的校验器配置。校验器(和字段校验器)必须有一个type属性,这里指向的是已经注册的校验器的名字。validators元素还必须有<param>元素,带有name和value属性来设置校验器实例的任何需要设置的参数。
(1)校验错误信息
每个validator元素必须在validator元素体内定义一个message元素。这个message元素有一个属性key,如果key没有复制,那么message标签的body会被当做默认的信息,如果校验失败它会被添加到Action中,key用来在Action的资源包里作为寻找一个信息的主键,如果Action实现了LocaleAware接口(ActionSupport已经实现了这个接口),那么会使用getText()来获取本地化信息。这个功能基于用户发起请求的Locale(或者任何设置到实现了LocaleAware的Action中的Locale)来提供本地化信息。
但不管是使用key值从资源包中获取信息还是使用默认的信息,当前的校验器被退回ValueStack中,然后消息被解析来处理其中的\${...}部分,\${和}中间的部分会被求值,然后替换相应的部分。使用校验器时抛出的错误信息中是可以携带参数的(如字段名称和门限值是多少等),如:
${filedname} 的值必须限于 ${min} 和 ${max}之间;
(2)用别名区分特定的校验规则
在struts.xml中几个action可以共同使用一个action类,如果它们之间的校验规则不相同,这种情况下一个validation文件是无法处理的。这个时候可以为每个action使用一个validation文件,虽然它们的类是相同的。只要在相同的目录下创建一个名为ActionName-aliasName-validation.xml的文件就可以。
(3)继承关系
框架也会搜索Action的层次关系树来查找Action父类以及实现的接口的校验规则。父类中定义的校验规则,子类会完全继承(这一点与上一节中类型转化的配置相似)。而且子类中的 *-validation.xml会覆盖父接口*-validation.xml。
(4)打开校验
为了开启一个action的校验,所需要做的就是在action的拦截器ref中包含ValidationInterceptor。就像这样:
<interceptor name="validator" class="com.opensymphony.xwork.validator.ValidationInterceptor"/>
这也是框架执行校验的原因所在,ValidationInterceptor做了一切工作:读取validation文件、检验数据、提示错误信息。
注意
默认的validationWorkflowStack已经包含了这个拦截器,也就是说action要配置了这个拦截器栈,校验框架才能生效。
7.2.3 注册校验器
校验规则是通过校验器来处理的,它们必须注册到ValidatorFactory(使用registerValidator方法)。最简单的方法就是添加一个文件名为validators.xml的文件,位置在CLASSPATH(/WEB-INF/classes)的根目录下,来声明所有的校验器。
这个列表显示了所有Struts2带来的校验器,如实例7-10所示。
【实例7-10】内置校验器:validators.xml
01 <validators> 02 <validator name="required" 03 class="com.opensymphony.xwork.validator.validators.RequiredFieldValidator"/> 04 <validator name="requiredstring" 05 class="com.opensymphony.xwork.validator.validators.RequiredStringValidator"/> 06 <validator name="int" 07 class="com.opensymphony.xwork.validator.validators.IntRangeFieldValidator"/> 08 <validator name="double" 09 class="com.opensymphony.xwork.validator.validators.DoubleRangeFieldValidator"/> 10 <validator name="date" 11 class="com.opensymphony.xwork.validator.validators.DateRangeFieldValidator"/> 12 <validator name="expression" 13 class="com.opensymphony.xwork.validator.validators.ExpressionValidator"/> 14 <validator name="fieldexpression" 15 class="com.opensymphony.xwork.validator.validators.FieldExpressionValidator"/> 16 <validator name="email" 17 class="com.opensymphony.xwork.validator.validators.EmailValidator"/> 18 <validator name="url" 19 class="com.opensymphony.xwork.validator.validators.URLValidator"/> 20 <validator name="visitor" 21 class="com.opensymphony.xwork.validator.validators.VisitorFieldValidator"/> 22 <validator name="conversion" 23 class="com.opensymphony.xwork.validator.validators.ConversionErrorFieldValidator"/> 24 <validator name="stringlength" 25 class="com.opensymphony.xwork.validator.validators.StringLengthFieldValidator"/> 26 <validator name="regex" 27 class="com.opensymphony.xwork.validator.validators.RegexFieldValidator"/> 28 </validators>
【代码剖析】上述校验器功能的详细情况如表7.1所示。
表7.1 校验器功能表
注意
如果自定义的校验器被定义而且创建了一个validators.xml文件并放在CLASSPATH中,记得复制所有其他需要的预定义的校验器到validators.xml里。如果不需要注册,则不要创建这个文件。一旦validators.xml在classpath里被检测到,默认的validators定义就不会被装载了,只有没发现自定义validators.xml的时候才会装载。
7.2.4 字段校验和非字段校验
非字段校验又称为简单校验器(例如ExpressionValidator)。执行校验检查不是绑定到单一的指定的字段上的。当-validation.xml文件中声明一个简单的校验器时,不需要把一个fieldname属性和它关联。
字段校验器(例如Email Validator)设置用来在一个单一字段上进行校验检查。它们要求在-validation.xml文件中执行一个fieldname属性。有两种不同的XML语法(但是等效)可以用来声明字段校验器。
1)定义简单校验器使用<validator>元素,如:
<validator type="expression> <param name="expression">foo > bar</param> <message>foo must be great than bar.</message> </validator>
2)<validator>元素也可以定义字段校验,包含了参数<param name="fieldName">
<!--包含参数的校验器--> <validator type="required"> <param name="fieldName">bar</param> </validator>
注意
因为校验规则是在一个XML文件里,必须转义特殊字符。例如,注意上面的expression校验器规则,使用“>”来代替“>”。参考一个XML相关的文档来了解必须转义的字符的全部列表。最常使用的必须转义的字符是&(使用&)、>(使用>)和<(使用<)。上面例子中“foo>bar”是“foo>bar”的转义。
3)定义字段校验还有一种方法是使用<field-validator>元素,如实例7-11所示。
【实例7-11】字段校验
<field name="email_address"> <field-validator type="required"> <message>You cannot leave the email address field empty.</message> </field-validator> <field-validator type="email"> <message>The email address you entered is not valid.</message> </field-validator> </field>
7.2.5 校验器的短路
使用short-circuit属性可以将校验器设置为短路,也就是如果此校验无法通过立刻结束校验过程,不再执行以下的其他校验器,如实例7-12所示。
【实例7-12】字段校验
<!--校验器的短路--> <field name="email"> <field-validator type="required" short-circuit="true"> <message>You must enter a value for email.</message> </field-validator> <field-validator type="email" short-circuit="true"> <message>Not a valid e-mail.</message> </field-validator> </field>
【代码剖析】上述代码中校验器执行的顺序如下:
1)简单校验器优先字段校验器。
2)都是简单校验器,按照它们定义的顺序被校验。
3)同是字段校验器,则按照它们定义的顺序来校验。
4)如果某个短路的校验器失败了,将会阻止其他后续的校验器的进行。
5)如果产生错误(action错误或者字段错误,取决于校验器的类型),它将会被添加到被校验的对象的ValidationContext中。
注意
一个字段校验器如果被短路了,仅会阻止其相同字段的字段校验器被执行,而不影响对别的字段的校验。
7.2.6 客户端校验
Struts2提供了客户端校验的支持。Struts2会生成校验的JavaScript发送到浏览器端。用户提交的时候,无须再传输数据到服务器端,直接在网页中检验结果,这种做法可以提高响应速度。如图7.2和图7.3所示,展示了服务器端校验和客户端校验流程的区别。
图7.2 服务器端校验流程图
图7.3 客户端校验流程图
它可以基于每个表单,通过设置<form>标签的validate="true"来启用:
<s:form name="test" action="javascriptValidation" validate="true"> ...... </s:form>
1. 纯JavaScript客户端校验
纯JavaScript客户端校验在xhtmltheme和css_xhtmltheme中使用,是最简单且包含最少客户端校验的校验。这种类型的校验使用了100%的客户端JavaScript代码来设法校验用户输入的数据。因为实际上校验逻辑在JavaScript里面重复进行,所以理解这种情况很重要。有些数据会被JavaScript代码认为是可接收的,而可能会被服务器端校验认为是不可接收的。有下列的校验器被支持:
❑ required validator(必填校验器)
❑ requiredstring validator(必填字符串校验器)
❑ stringlength validator(字符串长度校验器)
❑ regex validator(表达式校验器)
❑ email validator(邮件校验器)
❑ url validator(网址校验器)
❑ int validator(整数校验器)
❑ double validator(双精度数校验器)
因为客户端校验不与服务器端通信,xhtml theme和css_xhtml theme负责正确地操作HTML DOM以内置方式显示错误信息。负责来进行这个逻辑的JavaScript是validation.js,可以在每个theme里发现。
2. AJAX客户端校验
基于AJAX的客户端校验是对纯JavaScript客户端校验的改进,它仅在ajax theme中使用。它使用了多种技术的组合:JavaScript、DOM操作和通过DWR进行的远端服务器通信。不像纯客户端校验实现,基于AJAX的校验会和Server进行通信。这意味着所有校验的规则是:如果它在提交一个表单时工作,那么它依然会和浏览器一起工作,如图7.4所示。
图7.4 AJAX校验流程图
校验通过每个表单元素的onblur事件发生。当每一次用户输入一些内容以及移动到下一个表单元素时,输入的数据(以及和其他所有之前输入的数据)都会被发送到服务器端进行校验。整个校验stack都在进行,包括visitor校验器和action的validate()方法。
如果遇到一个错误,就像纯客户端校验一样,HTML和DOM会立刻被更新。
7.2.7 AJAX校验实例
本节将以一个例子说明如何实现AJAX校验,首先看看基本的表单校验是如何做的。
1)写一个html表单。在这个form表单里有三个等待提交的字段,如实例7-13所示。
【实例7-13】实现AJAX基本校验的输入页面:quiz-ajax.jsp
<%@ taglib prefix="s" uri="/struts-tags"%> <%@ taglib prefix="sx" uri="/struts-dojo-tags"%> <html> <head> <title>Validation - Ajax</title> <sx:head cache="false" compressed="false" /> </head> <body> <div id="response" style="border: 1px solid black"> Response goes here </div> <br /> <!--关于表单的设置--> <s:form method="post" theme="xhtml" namespace="/validat" action="quizAjax" id="form"> <s:textfield label="Name" name="name" /> <!--关于用户名输入框--> <s:textfield label="Age" name="age" /> <!--关于年龄输入框--> <s:textfield label="Favorite color" name="answer" /> <!--关于颜色输入框--> <sx:submit validate="true" ajaxAfterValidation="true" targets="response" showLoadingText="false" /> <!--关于提交按钮 --> </s:form> </body> </html>
【代码剖析】在上述代码中,当单击“提交”按钮时,程序就会交由名为quizAjax的Action来处理。
2)写一个action类,在其中定义了3个字段,如实例7-14所示。
【实例7-14】处理请求的Action:QuizAction.java
01 //使用了自动校验, 无须在Action中添加任何代码, 但必须继承于ActionSupport 02 public class QuizAction extends ActionSupport { 03 //简单属性列表 04 String name; 05 int age; 06 String answer; 07 //以下是属性的setter/getter方法 08 public String getName() { //关于属性name的getter和setter方法 09 return name; 10 } 11 public void setName(String name) { 12 this.name = name; 13 } 14 public int getAge() { //关于属性age的getter和setter方法 15 return age; 16 } 17 public void setAge(int age) { 18 this.age = age; 19 } 20 public String getAnswer() { //关于属性answer的getter和setter方法 21 return answer; 22 } 23 public void setAnswer(String answer) { 24 this.answer = answer; 25 } 26 //空方法, 直接返回 27 public int execute(){ 28 //返回成功字符串 29 return SUCCEED; 30 } 31 }
【代码剖析】在上述代码中,首先创建了关于传递请求的各种参数属性,然后设置这些属性的getter和setter方法,最后在execute()方法中直接返回字符串SUCCEED。
填写要使用的校验器,validation.xml的格式是<ActionClassName>-validation.xml。在本例中文件名为QuizAction-validation.xml,如实例7-15所示。
【实例7-15】关于基本校验的XML文件:QuizAction-validation.xml
01 <!-- 02 Add the following DOCTYPE declaration as first line of your XXX-validation.xml file: 03 <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" 04 "http://sw.opensymphony.com/xwork/xwork-validator-1.0.2.dtd"> 05 --> 06 <!--使用基本校验器 --> 07 <validators> 08 <field name="name"> <!--关于名字的校验--> 09 <field-validator type="requiredstring"> 10 <message>You must enter a name</message> 11 </field-validator> 12 </field> 13 <field name="age"> <!--关于年龄的校验--> 14 <field-validator type="int"> 15 <param name="min">13</param> 16 <param name="max">19</param> 17 <message>Only people ages 13 to 19 may take this quiz</message> 18 </field-validator> 19 </field> 20 </validators>
【代码剖析】在上述代码中实现了一些基本校验,在第8行到第12行实现了对于用户名必须填写的校验,在第13行到第19行实现了所填入的年龄必须在13到19之间的校验。
如果想实现客户端校验,只需把给form标签加上validate属性就可以了。
<s:form method="post" validate="true">
如果实现AJAX校验,有以下几个步骤:
1)把form主题设为AJAX。
<s:form method="post" validate="true" theme="ajax">
2)给应用程序配置好DWR,DWR通过位于/WEB-INF/目录下的dwr配置(dwr.xml)来设置,如实例7-16所示。
【实例7-16】关于DWR的配置:dwr.xml
01 <!DOCTYPE dwr PUBLIC 02 "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN" 03 "http://sw.getahead.ltd.uk/dwr/dwr10.dtd"> 04 <dwr> 05 <allow> <!--关于标签allow的使用--> 06 <create creator="new" javascript="validator"> 07 <param name="class" 08 value="com.opensymphony.webwork.validators.DWRValidator" /> 09 </create> 10 <convert converter="bean" 11 match="com.opensymphony.xwork.ValidationAwareSupport" /> 12 </allow> 13 <signatures> <!--关于标签signatures的使用--> 14 <![CDATA[ 15 import java.util.Map; 16 import com.opensymphony.webwork.validators.DWRValidator; 17 DWRValidator.doPost(String, String,Map<String, String>); 18 ]]> 19 </signatures> 20 </dwr>
3)需要注册一个DWRServlet到Web应用程序中。下面显示了一个典型的配置了DWR Servlet的web.xml,如实例7-17所示。
【实例7-17】关于web文件的配置
<servlet> <!--在servlet中配置DWR类 --> <servlet-name>dwr</servlet-name> <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> </servlet> <servlet-mapping> <!--配置servlet映射--> <servlet-name>dwr</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping>
【代码剖析】上述代码主要用来实现配置DWRServlet类和关于该类的映射。