构建带有验证的 “防弹” 应用程序
验证的重要性
伟大的篮球队经常因为他们有稳定的控球、精彩的传球、挡不住的投篮而受到赞扬。伟大的音乐家要反复地练习,而且会在一两个音阶或小节上花费数小时的时间。伟大的汽车有许多额外的特性,但是跑得更远,引擎问题更少,而比它们弱小的竞争对手更可靠。所有这些例子都表明,伟大的执行者 “做小事”。这些小事可能不明显,但是会带来一致而积极的结果。
对于应用程序编程,花里胡哨的用户界面和没有必要的复杂的线程,在保证用户数据正确方面,可不如对用户的冲击那样大。没有什么比在 Web 表单中输入错误的电子邮件地址、电话号码,还要让应用程序傻乎乎地继续执行更糟的了。在这类情况下,真正能够有帮助的小事就是验证。
简而言之,验证就是确保数据正确。这听起来有点傻,但是正确 可能有各种不同的含义,这也正是验证发挥威力的地方。一个正确的电子邮件地址可能只是拥有至少一个 @
符号,在 @
符号后有一个句点(.
)。但是,更复杂的应用程序可能还要求电子邮件地址是指定长度、来自少数特定域。在任何一种情况下,代码都必须保证输入的数据是可以接受的;否则,就会产生各种可能的错误情况:
- 可能把错误的数据传递到另一个方法,后一个方法不得不执行额外的错误检查,从而降低应用程序的速度。
- 可能把错误的数据传递给一个不 执行错误检测的方法,从而造成应用程序崩溃。
- 可能把错误的数据插入数据库,从而造成下次(可能是 5 分钟或者 5 天以后)读取数据时的错误。
在所有这些情况中,都将给用户带来损害,并给他们提供了转到竞争对手 Web 站点或产品的好理由。
本教程的主题是验证。保证不是什么令人兴奋的、迷人的主题,但是它是可以让好应用程序变成优秀的那些 “小事” 中的一件。
从服务器端验证开始
在 Struts 这样的服务器端编程环境中,最容易的(在某种程度上,也是最自然的)验证方式就是在 Java 代码中放几个检测。具体来说,用于从表单接收数据的 Struts Action
似乎是放置验证代码的好地方。可以从 servlet 请求中提取值,并检查电子邮件或电话号码的格式、名称的长度或者邮编的合法性。然后,如果有问题,可以再次向用户显示提交表单,可能还带有错误消息,指出发生的问题。
虽然这可能现在听起来不错,但是执行服务器端验证是获得良好用户体验的最糟方式之一。当用户在 Web 表单上点击 Submit、Save 或 Enter ,并且页面闪烁时(表明服务器上正在发生什么),他们可不想再次回到原来的提交页面,还带着错误消息。如果需要花很长时间来处理请求,那么这个问题就是复杂的:用户必须等待,然后应用程序返回一组错误消息。Web 是种步伐很快的媒体,用户期望最小延迟、响应快的应用程序以及动态的用户界面。
如果不在服务器上处理错误,则应当试着把验证移到客户机。客户端验证可以防止用户提交包含错误数据的表单。因为没有通信的延迟,所以它也更快。现在,好的应用程序中几乎或多或少都有一些客户端验证。
但是,也有些时候服务器端验证 —— 或者至少某种形式的服务器端验证 —— 是有意义的。有时验证包含格式、长度和其他细小问题的检查(想想电话号码、邮编、区号和其他数据);其他时候,验证要调用更复杂的计算。比如,可能需要确保某本书可以运送到某个邮编的地区。在这些情况下,可能不得不允许服务器请求,因为可能需要数据库和其他复杂处理才能保证正确的响应。但是,用户一般能够理解这些是更复杂的请求,所以一般会耐心等待响应。实际上,这类计算很少被当作验证,而是和应用程序服务的通用业务混在一起。同样,除了这些最复杂的情况之外,只要有可能,都应当把验证移到客户机。
移到 JavaScript 进行验证
客户端验证最明显的选择就是 JavaScript。这个流行的脚本语言易于学习、灵活,而且在每个现代 Web 浏览器中都可用(虽然会有所差别)。对于许多应用程序来说,JavaScript 足够好了。但是,仍然有些不足。
首先,JavaScript 仍然是代码,所以就像其他代码一样,必须编写、测试、调试和部署 JavaScript。如果从 5 位邮编转到 9 位邮编,那么验证代码就要改变,而且需要在它们影响的所有页面上测试修改。虽然这项工作对于得到清爽的、让用户高兴的结果来说很值得,但却是一项时间密集型工作。
在使用 JavaScript 时,也会非常麻烦 —— 可以容易地把 scriplet 放进 HTML 页面,但是会给维护带来恶梦。虽然进行限制并不太难,但是有种诱惑就是只对页面内的脚本做 “一次快速修正”,却从来不删除这段脚本并重写 JavaScript 库中的代码。
最后,可能必须要一个项目一个项目都带着 JavaScript 库。这也不算什么大麻烦,但是个问题 —— 对于部署代码的每个平台都要测试该代码。
请不要误解我 —— JavaScript 是验证的优秀解决方案,但是如果用 Struts,那么还会有第三个 —— 甚至更好的选项。
了解 Struts Validator
Struts 提供了一个名为 Validator 的优秀组件。Validator 可以插入 Struts 应用程序(将在 安装 Validator 框架 中介绍),甚至直接和最新的 Struts 发行版绑定在一起。只要几个 JAR 文件就可以了。但是 Validator 强在什么地方呢?为什么要用它代替 JavaScript 呢?
而且,您应当认识到,Validator 的大部分执行都使用 JavaScript。所以实际上并没有离开 JavaScript,而且得到的客户端验证也是 JavaScript 擅长的内容。但是,Validator 消除了 JavaScript 的许多问题。首先,它是由成千上万的 Struts 开发人员和用户编码、测试和调试过的,因此降低了您需要进行的测试数量。(我绝对不是 暗示您不要测试;Validator 只是降低了测试负担,但并没有完全消除它。)另外,Validator 提供了大量常用验证函数,所以您不必为电子邮件地址、电话号码、邮编以及其他常用数据编写验证器。这棒不棒?
而最重要的,可能是 Struts Validator 主要通过配置文件工作,而不用内联的 HTML 代码。通过简单的 XML 文件,可以指示要验证哪个字段,要执行哪类验证。Struts 和 Validator 负责把配置变成工作的 JavaScript 代码,您这一边不需要做任何额外工作!虽然偶尔也要为特定于应用程序的数据添加新的验证函数,但是在 HTML 中使用这些函数的工作由 Struts 处理 —— 不需要手工过程。这就是 Validator 真正胜出而珍贵的地方。被说服了么?现在来看它。
设置示例应用程序
从 struts-blank WAR 开始
在介绍如何使用 Validator 之前,需要利用一个示例应用程序。如果用二进制发行版安装了 Struts(细节请参阅 附录),那么就有了一个好的起点。请确保 Tomcat 正在运行,并导航到 http://localhost:8080/struts-blank。应当看到像图 1 所示的内容:
图 1. struts-blank 应用程序提供了新应用程序的模板
这页上的文本明确地说明了需要做的事情:
- 找到磁盘上的 struts-blank.war 文件。(在我的机器上,是 /usr/local/jakarta-tomcat-5.5.9/webapps/struts-blank.war。)
- 把这个文件拷贝到可以处理它的地方,或者在开发目录中,或者在桌面上。
- 把 test-validation.war 文件改名。这个文件将是在这篇文章中要开发的验证应用程序的基础。
现在需要把 WAR 文件展开到一组目录中,这样可以方便地处理。请创建一个在其中操作的目录; 我通常给它起名为 staging 或类似的名称。创建了目录之后,请用 jar
命令,使用 xvf
选项把这个文件展开到新建的目录中,如清单 1 所示:
清单 1. WAR 文件目录
[bmclaugh:/usr/local/java]$ ls
jakarta-struts-1.2.4 src xalan-j_2_6_0
jakarta-tomcat-5.5.9 test-validation.war
[bmclaugh:/usr/local/java]$ mkdir staging
[bmclaugh:/usr/local/java]$ cd staging
[bmclaugh:/usr/local/java/staging]$ jar xvf ../test-validation.war
created: META-INF/
inflated: META-INF/MANIFEST.MF
created: WEB-INF/
created: WEB-INF/classes/
created: WEB-INF/classes/resources/
created: WEB-INF/lib/
created: WEB-INF/src/
created: WEB-INF/src/java/
created: WEB-INF/src/java/resources/
created: pages/
inflated: WEB-INF/classes/MessageResources.properties
inflated: WEB-INF/classes/resources/MessageResources.properties
inflated: WEB-INF/lib/commons-beanutils.jar
inflated: WEB-INF/lib/commons-collections.jar
inflated: WEB-INF/lib/commons-digester.jar
inflated: WEB-INF/lib/commons-fileupload.jar
inflated: WEB-INF/lib/commons-logging.jar
inflated: WEB-INF/lib/commons-validator.jar
inflated: WEB-INF/lib/jakarta-oro.jar
inflated: WEB-INF/lib/struts.jar
inflated: WEB-INF/src/README.txt
inflated: WEB-INF/src/build.xml
inflated: WEB-INF/src/java/resources/application.properties
inflated: WEB-INF/struts-bean.tld
inflated: WEB-INF/struts-config.xml
inflated: WEB-INF/struts-html.tld
inflated: WEB-INF/struts-logic.tld
inflated: WEB-INF/struts-nested.tld
inflated: WEB-INF/struts-tiles.tld
inflated: WEB-INF/tiles-defs.xml
inflated: WEB-INF/validation.xml
inflated: WEB-INF/validator-rules.xml
inflated: WEB-INF/web.xml
inflated: index.jsp
inflated: pages/Welcome.jsp
|
可以对这些文件做修改,根据个人需要定制应用程序。这种方法的好处是所有的设置文件都有了,不用考虑要下载所有正确的 WAR 文件。有哪个开发人员不喜欢这么好的方便呢?
改变欢迎页面
首先,需要在主页面上添加链接;这个链接将把用户带到一个简单的表单,在这个表单上演示一些验证规则。如果访问示例应用程序,可以迅速地看到显示的默认页面是保存在应用程序结构的页面目录中的 Welcome.jsp。请在文本处理器或 IDE 中打开这个文件。
struts-blank WAR 提供的版本是个好的开始;要改变显示的文本,甚至不需要触动这个文件以及保存在 MessageResources 文件中的文本(我稍后就会介绍)。所有需要做的,只是添加一个简单的链接。请把清单 2 中的粗体行添加到 Welcome.jsp 文件:
清单 2. Welcome.jsp 文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>
<html:html locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<logic:notPresent name="org.apache.struts.action.MESSAGE" scope="application">
<font color="red">
ERROR: Application resources not loaded -- check servlet container
logs for error messages.
</font>
</logic:notPresent>
<h3><bean:message key="welcome.heading"/></h3>
<p><bean:message key="welcome.message"/></p>
<p>
<ul>
<li><html:link action="/TestSimpleValidation">
<bean:message key="welcome.test-validation" />
</html:link></li>
</ul>
</p>
</body>
</html:html>
|
html:link
标记是 Struts 中非常基本的构造。在这里,用它链接到一个 Struts Action
,后者将装入一个有一些验证示例的页面。但在介绍它之前,要修改一些属性(welcome.title
、welcome.heading
和 welcome.message
),还要添加一个属性(welcome.test-validation
)。
这些属性在 MessageResources.properties 文件中,该文件在应用程序结构的 WEB-INF/classes 目录中。请打开这个文件,到文件的底部,按下面粗体的部分做修改:
# -- welcome --
welcome.title=Struts Validator Test Application
welcome.heading=Welcome to the Validation Tester Application!
welcome.message=
This is a simple application meant to test and demonstrate the Struts Validator component.
welcome.test-validation=Test out some simple uses of the Struts Validator
|
现在只需要添加一个简单的 forward
到 struts-config.xml 文件(这个文件应当在应用程序的 WEB-INF 文件夹中)。查找 action-mappings
元素并添加下面的内容:
<action-mappings>
<action path="/TestSimpleValidation"
forward="/pages/test-validation.jsp" />
<-- Other Action mappings -->
</action-mappings>
|
做了这些修改,初始的欢迎页面即已就绪。在下一节,将重新部署应用程序,测试这个页面。
重新部署应用程序
每次重新部署的时候,只要做几个简单步骤:
- 用
jar
命令把应用程序压缩回 WAR 文件,如清单 3 所示:
清单 3. jar 命令
[bmclaugh:/usr/local/java]$ cd staging
[bmclaugh:/usr/local/java/staging]$ jar cvf ../test-validation.war *
added manifest
ignoring entry META-INF/
ignoring entry META-INF/MANIFEST.MF
adding: WEB-INF/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/MessageResources.properties(in = 1167)
(out= 488)(deflated 58%)
adding: WEB-INF/classes/resources/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/resources/MessageResources.properties(in = 1480)
(out= 655)(deflated 55%)
adding: WEB-INF/lib/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/lib/commons-beanutils.jar(in = 118726) (out= 105729)
(deflated 10%)
adding: WEB-INF/lib/commons-collections.jar(in = 175426) (out= 144506)
(deflated 17%)
adding: WEB-INF/lib/commons-digester.jar(in = 109096) (out= 99033)
(deflated 9%)
adding: WEB-INF/lib/commons-fileupload.jar(in = 22379) (out= 19246)
(deflated 13%)
adding: WEB-INF/lib/commons-logging.jar(in = 38015) (out= 34595)
(deflated 8%)
adding: WEB-INF/lib/commons-validator.jar(in = 84260) (out= 76342)
(deflated 9%)
adding: WEB-INF/lib/jakarta-oro.jar(in = 65261) (out= 56142)
(deflated 13%)
adding: WEB-INF/lib/struts.jar(in = 526578) (out= 480962)
(deflated 8%)
adding: WEB-INF/src/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/src/build.xml(in = 3672) (out= 1080)(deflated 70%)
adding: WEB-INF/src/java/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/src/java/resources/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/src/java/resources/application.properties(in = 1480)
(out= 655)(deflated 55%)
adding: WEB-INF/src/README.txt(in = 1923) (out= 837)(deflated 56%)
adding: WEB-INF/struts-bean.tld(in = 8860) (out= 771)(deflated 91%)
adding: WEB-INF/struts-config.xml(in = 6665) (out= 1994)(deflated 70%)
adding: WEB-INF/struts-html.tld(in = 67559) (out= 2069)(deflated 96%)
adding: WEB-INF/struts-logic.tld(in = 14731) (out= 830)(deflated 94%)
adding: WEB-INF/struts-nested.tld(in = 65059) (out= 2086)(deflated 96%)
adding: WEB-INF/struts-tiles.tld(in = 7842) (out= 717)(deflated 90%)
adding: WEB-INF/tiles-defs.xml(in = 1379) (out= 535)(deflated 61%)
adding: WEB-INF/validation.xml(in = 2121) (out= 550)(deflated 74%)
adding: WEB-INF/validator-rules.xml(in = 12254) (out= 1628)(deflated 86%)
adding: WEB-INF/web.xml(in = 1942) (out= 582)(deflated 70%)
adding: index.jsp(in = 276) (out= 188)(deflated 31%)
adding: pages/(in = 0) (out= 0)(stored 0%)
adding: pages/Welcome.jsp(in = 657) (out= 318)(deflated 51%)
|
- 用
jar
的 tvf
选项可以确保 WAR 文件包含所有应当包含的内容:
清单 4. tvf 选项
[bmclaugh:/usr/local/java/staging]$ cd ..
[bmclaugh:/usr/local/java]$ jar tvf test-validation.war
0 Thu May 19 17:13:34 CDT 2005 META-INF/
70 Thu May 19 17:13:34 CDT 2005 META-INF/MANIFEST.MF
0 Thu May 19 16:51:20 CDT 2005 WEB-INF/
0 Thu May 19 16:55:00 CDT 2005 WEB-INF/classes/
1167 Thu May 19 16:55:00 CDT 2005 WEB-INF/classes/MessageResources.properties
0 Thu May 19 16:51:20 CDT 2005 WEB-INF/classes/resources/
1480 Thu May 19 16:51:20 CDT 2005 WEB-INF/classes/resources/
MessageResources.properties
0 Thu May 19 16:51:20 CDT 2005 WEB-INF/lib/
118726 Thu May 19 16:51:20 CDT 2005 WEB-INF/lib/commons-beanutils.jar
175426 Thu May 19 16:51:20 CDT 2005 WEB-INF/lib/commons-collections.jar
109096 Thu May 19 16:51:20 CDT 2005 WEB-INF/lib/commons-digester.jar
22379 Thu May 19 16:51:20 CDT 2005 WEB-INF/lib/commons-fileupload.jar
38015 Thu May 19 16:51:20 CDT 2005 WEB-INF/lib/commons-logging.jar
84260 Thu May 19 16:51:20 CDT 2005 WEB-INF/lib/commons-validator.jar
65261 Thu May 19 16:51:20 CDT 2005 WEB-INF/lib/jakarta-oro.jar
526578 Thu May 19 16:51:20 CDT 2005 WEB-INF/lib/struts.jar
0 Thu May 19 16:51:20 CDT 2005 WEB-INF/src/
3672 Thu May 19 16:51:20 CDT 2005 WEB-INF/src/build.xml
0 Thu May 19 16:51:20 CDT 2005 WEB-INF/src/java/
0 Thu May 19 16:51:20 CDT 2005 WEB-INF/src/java/resources/
1480 Thu May 19 16:51:20 CDT 2005 WEB-INF/src/java/resources/application.properties
1923 Thu May 19 16:51:20 CDT 2005 WEB-INF/src/README.txt
8860 Thu May 19 16:51:20 CDT 2005 WEB-INF/struts-bean.tld
6665 Thu May 19 16:51:20 CDT 2005 WEB-INF/struts-config.xml
67559 Thu May 19 16:51:20 CDT 2005 WEB-INF/struts-html.tld
14731 Thu May 19 16:51:20 CDT 2005 WEB-INF/struts-logic.tld
65059 Thu May 19 16:51:20 CDT 2005 WEB-INF/struts-nested.tld
7842 Thu May 19 16:51:20 CDT 2005 WEB-INF/struts-tiles.tld
1379 Thu May 19 16:51:20 CDT 2005 WEB-INF/tiles-defs.xml
2121 Thu May 19 16:51:20 CDT 2005 WEB-INF/validation.xml
12254 Thu May 19 16:51:20 CDT 2005 WEB-INF/validator-rules.xml
1942 Thu May 19 16:51:20 CDT 2005 WEB-INF/web.xml
276 Thu May 19 16:51:20 CDT 2005 index.jsp
0 Thu May 19 16:53:24 CDT 2005 pages/
657 Thu May 19 16:51:20 CDT 2005 pages/Welcome.jsp
|
- 把新的 WAR 文件拷贝到 Tomcat 的 webapps 目录:
[bmclaugh:/usr/local/java]$ cp test-validation.war jakarta-tomcat-5.5.9/webapps/
|
- 在 http://localhost:8080/test-validation/ 上访问应用程序。
可以编写脚本或 Ant 文件来完成这些任务,也可以手工执行这些任务。在任何情况下,一修改应用程序(在本教程中就会做大量修改)就重新部署都是很好的快速方式。
检查欢迎页面
应用程序重新部署之后,请访问 http://localhost:8080/test-validation/。应当看到像图 2 一样的页面:
图 2. 对 Welcome.jsp 和消息资源文件的修改创建了新的欢迎屏幕
这时,应用程序还没有功能;在新链接上点击只会出现讨厌的错误。但这就对了 —— 已经得到了好的开始,可以直接使用 Validator 了。
安装 Validator 框架
必要的库
Struts Validator 是一个组成相当复杂的软件,而且它依赖于其他几个库才能正常工作。就像在 servlet 引擎中运行 Struts 需要多个 JAR 文件一样,要让 Validator 工作也还需要多个 JAR 文件。最重要的是需要 Jakarta ORO 包,它负责处理正则表达式。
Validator 还使用 Jakarta Commons BeanUtils、Jakarta Commons Logging、Jakarta Commons Collections 和 Jakarta Commons Digester 包。这些 JAR 文件需要放在 Tomcat 的 common/lib 目录中或 Web 应用程序的 WEB-INF/lib 目录中。
最后,Struts Validator 是建立于 Jakarta Commons 包 Jakarta Commons Validator 以及其他包的基础之上的。所以需要另一个 JAR 文件。这些 JAR 文件已经很多了,但是要处理的还有更多。但是在开始下载之前,先继续阅读。我介绍完所有要求之后,我还会介绍一种快捷方式,不用自己下载并手工配置,就可以在应用程序中得到 Validator 支持。
验证规则
Validator 库就位之后,还需要两个 XML 文件:validation-rules.xml 和 validator.xml。validation-rules.xml 文件或多或少是静态的,所以先处理它。这个文件指定可用的验证规则;因为 Validator 自带了几个默认规则,所以只要找到这个文件的工作目录,并把它拷贝到自己应用程序的 WEB-INF 目录即可。
这个文件很长,所以我在清单 5 中只包含一小段来介绍它的样子:
清单 5. validation-rules.xml 文件
<DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<form-validation>
<global>
<validator name="required"
classname="org.apache.struts.validator.FieldChecks"
method="validateRequired"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
msg="errors.required"/>
<validator name="requiredif"
classname="org.apache.struts.validator.FieldChecks"
method="validateRequiredIf"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"
msg="errors.required"/>
<validator name="validwhen"
msg="errors.required"
classname="org.apache.struts.validator.validwhen.ValidWhen"
method="validateValidWhen"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"/>
<validator name="minlength"
classname="org.apache.struts.validator.FieldChecks"
method="validateMinLength"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionMessages,
javax.servlet.http.HttpServletRequest"
depends=""
msg="errors.minlength"
jsFunction="org.apache.commons.validator.javascript.validateMinLength"/>
<!--
This simply allows struts to include the validateUtilities into a page, it should
not be used as a validation rule.
-->
<validator name="includeJavaScriptUtilities"
classname=""
method=""
methodParams=""
depends=""
msg=""
jsFunction="org.apache.commons.validator.javascript.validateUtilities"/>
</global>
</form-validation>
|
不要太过担心这个文件的复杂性;除非要编写自己的验证规则,否则只要接受并使用它的默认版本就可以了。
向应用程序添加错误消息
大多数版本的 validation-rules.xml 在文件头的注释中,都包含像清单 6 这样的内容:
清单 6. validation-rules.xml 注释
These are the default error messages associated with
each validator defined in this file. They should be
added to your projects ApplicationResources.properties
file or you can associate new ones by modifying the
pluggable validators msg attributes in this file.
# Struts Validator Error Messages
errors.required={0} is required.
errors.minlength={0} can not be less than {1} characters.
errors.maxlength={0} can not be greater than {1} characters.
errors.invalid={0} is invalid.
errors.byte={0} must be a byte.
errors.short={0} must be a short.
errors.integer={0} must be an integer.
errors.long={0} must be a long.
errors.float={0} must be a float.
errors.double={0} must be a double.
errors.date={0} is not a date.
errors.range={0} is not in the range {1} through {2}.
errors.creditcard={0} is an invalid credit card number.
errors.email={0} is an invalid e-mail address.
|
注释顶部的消息是条好建议。不管您是否遵照指示操作,Validator 在输出错误消息时都会采用这些属性。如果没有把这些插入应用程序的资源,结果就是空的错误消息,当然看起来就像根本没有错误消息一样。对于用户来说,再也没有什么会比提交表单之后、表单被拒绝、然后出错的时候没有任何反馈这件事更郁闷的了。
这些属性最好放在文件中,例如 MessageResources.properties,通常位于应用程序的 WEB-INF/classes 目录中:
清单 7. MessageResources.properties
<b># -- standard errors --
errors.header=<UL>
errors.prefix=<LI>
errors.suffix=</LI>
errors.footer=</UL>
# -- validator --
errors.invalid={0} is invalid.
errors.maxlength={0} can not be greater than {1} characters.
errors.minlength={0} can not be less than {1} characters.
errors.range={0} is not in the range {1} through {2}.
errors.required={0} is required.
errors.byte={0} must be an byte.
errors.date={0} is not a date.
errors.double={0} must be an double.
errors.float={0} must be an float.
errors.integer={0} must be an integer.
errors.long={0} must be an long.
errors.short={0} must be an short.
errors.creditcard={0} is not a valid credit card number.
errors.email={0} is an invalid e-mail address.
# -- other --
errors.cancel=Operation cancelled.
errors.detail={0}
errors.general=The process did not complete. Details should follow.
errors.token=Request could not be completed. Operation is not in sequence.</b>
# -- welcome --
welcome.title=Struts Validator Test Application
welcome.heading=Welcome to the Validation Tester Application!
welcome.message=This is a simple application meant to test and demonstrate the Struts Validator component.
welcome.test-validation=Test out some simple uses of the Struts Validator
|
但是,还是不用着急做这些修改,因为有更容易的方式(研究过 WEB-INF/classes/MessageResources.properties 文件的人可能对这个快捷方式已经有了认识)。如果想使用不同的错误消息,可以在这里做修改。例如,如果想让无效电子邮件地址的出错更好一些,可以把它改成像下面这样:
errors.email=You have entered an invalid e-mail address ({0}). Please try again.
errors.email=You have entered an invalid e-mail address ({0}). Please try again.
|
这是非常好的定制错误消息的方法,不用触及实际的代码行(从而避免了重新编译、重新部署和许多重新测试)。
把 Validator 连接到应用程序
现在需要让 Struts 应用程序知道 Validator。在这里,我指的是组件的整体,而不是 Validator 的某个特定应用(这是我将在 在应用程序中使用 Validator 中介绍的内容)。将需要使用 Struts 的 plugin
元素,这是让 Struts 知道它应当集成进应用程序的组件的方法。在 struts-config.xml 文件中需要以下条目,这个文件位于应用程序的 WEB-INF 目录下:
<!-- =================================================== Validator plugin -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property
property="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
</plug-in>
|
className
属性告诉 Struts 要装载哪个类;这个类应当实现了 Struts 的 PlugIn
接口,就像 ValidatorPlugIn
做的那样。然后,可能有许多特定于插件的 set-property
条目;对于 Validator,只需要一个:把 pathnames
属性设置为 validator-rules.xml 和 validation.xml 文件所在路径的值。如果想把这些值保存在其他地方,可以在 plugin
元素中指定替代位置。
这些步骤完成之后,需要做的就是编写特定于应用程序的表单、动作和验证需求的 validation.xml 文件。但是,先让我提供一些快捷方式,以避免 Validator 冗长的设置。
struts-blank 应用程序的值
如果在前面一直遵照我的指示,把 struts-blank.war 文件拷贝到新的文件 —— 用它作为开发 Struts 应用程序的基础 —— 那么就揭开了一个巨大的秘密:struts-blank.war 预先已经做好了使用 Struts Validator 的配置!请看一下示例应用程序的 WEB-INF/lib 文件夹,将看到所有 Validator 需要的库:
-
commons-beanutils.jar: Commons BeanUtils
-
commons-collections.jar: Commons Collections
-
commons-digester.jar: Commons Digester
-
commons-logging.jar: Commons Logging
-
commons-validator.jar: Commons Validator
-
jakarta-oro.jar: Jakarta ORO
如果查看 WEB-INF/classes/MessageResources.properties,会看到定义了全部 Validator error
属性。在 WEB-INF 中,将看到默认版本的 validation-rules.xml,它包含 Validator 的全部默认验证规则,都已经准备好了。有一个非常基本的 validation.xml 版本可供修改(本教程下一节就要介绍它)。而且,最好的是,struts-config.xml 已经设置好了 Validator plugin
元素。
这就是 struts-blank.war 真正美妙之所在,至少以我的观点是这样的:我从不需要记住需要什么 JAR 文件和配置步骤才能让 Validator 工作。我只是把这个文件拷贝到新的位置,把它改成应用程序的名称,然后就开始工作。即使一定要对现有的类或文件做些修改(例如添加链接到欢迎页,或删除某些 JSP),不用担心 Validator 的设置也是值得的。
您可能喜欢在每次开发新的 Struts 应用程序时都执行这一节描述的步骤,但是我宁愿拷贝 struts-blank.war 并改名,然后就开始工作。
在应用程序中使用 Validator
创建测试验证的表单
当我们离开示例应用程序时,有了一个欢迎页面和一个到 pages/test-validation.jsp 的链接。现在可以让这个页面就位了。开始时,它只是一个普通的老式 JSP 页面,有一个基本的表单;一旦让简单的 Struts 应用程序运行起来,我将介绍如何添加验证逻辑。现在,先从清单 8 显示的基本 JSP 开始:
清单 8. 基本的 JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<html:xhtml />
<html>
<head>
<title><bean:message key="valid.title" /></title>
</head>
<html:form action="/SubmitValid" focus="username">
<table border="0" width="100%">
<tr>
<th align="right">
<bean:message key="prompt.username" />:
</th>
<td align="left">
<html:text property="username" size="10" maxlength="10" />
</td>
</tr>
<tr>
<th align="right">
<bean:message key="prompt.password" />:
</th>
<td align="left">
<html:password property="password" size="16" maxlength="16"
redisplay="false" />
</td>
</tr>
<tr>
<th align="right">
<bean:message key="prompt.phone" />:
</th>
<td align="left">
<html:text property="phone" size="14" maxlength="14" />
</td>
</tr>
<tr>
<th align="right">
<bean:message key="prompt.email" />:
</th>
<td align="left">
<html:text property="email" size="20" maxlength="100" />
</td>
</tr>
<tr>
<th align="right">
<bean:message key="prompt.url" />:
</th>
<td align="left">
<html:text property="url" size="20" maxlength="100" />
</td>
</tr>
<tr>
<td align="right">
<html:reset />
</td>
<td align="left">
<html:submit property="Submit" value="Submit" />
</td>
</table>
</html:form>
</body>
</html>
|
要添加到列表中的内容很少。可以看到,它创建了一个表单,然后提供了输入错误数据的丰富机会。请注意对名称属性的丰富应用;现在应当把这些添加到 WEB-INF/classes/MessageResources.properties 文件中。把它们放在文件底部现有条目的后面:
# -- validation test page --
valid.title=Simple Validation Test Form
prompt.username=Username
prompt.password=Password
prompt.phone=Phone Number
prompt.email=E-Mail Address
prompt.url=URL (Website Address)
|
注意: 在本教程的这样简单的一个 JSP 中使用属性看起来有点过分。但是,这只是一个良好的编码实践。可以本地化这些属性,方便地修改它们和重用它们,而几乎不需要额外的开发时间。请习惯于利用这类最佳实践的优势,即使在示例应用程序和原型设计中也该如此。它将带来长期回报。
试着访问这个页面,会生成错误;还有许多工作要做(有时让 Struts 好的事情 —— 声明性异常、高度可配置的表单等等 —— 反而让它难以迅速就位运行)。在这一阶段,应当得到类似清单 9 所示的消息:
清单 9. 错误消息
javax.servlet.ServletException: Cannot retrieve mapping for action /SubmitValid org.apache.jasper.runtime.PageContextImpl.doHandlePageException(
PageContextImpl.java:848)
org.apache.jasper.runtime.PageContextImpl.handlePageException(
PageContextImpl.java:781)
org.apache.jsp.pages.test_002dvalidation_jsp._jspService(
org.apache.jsp.pages.test_002dvalidation_jsp:102)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:97)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
org.apache.jasper.servlet.JspServletWrapper.service(
JspServletWrapper.java:322)
org.apache.jasper.servlet.JspServlet.serviceJspFile(
JspServlet.java:291)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:241)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
org.apache.struts.action.RequestProcessor.doForward(
RequestProcessor.java:1056)
org.apache.struts.tiles.TilesRequestProcessor.doForward(
TilesRequestProcessor.java:261)
org.apache.struts.action.RequestProcessor.internalModuleRelativeForward(
RequestProcessor.java:994)
org.apache.struts.tiles.TilesRequestProcessor.internalModuleRelativeForward(
TilesRequestProcessor.java:343)
org.apache.struts.action.RequestProcessor.processForward(
RequestProcessor.java:553)
org.apache.struts.action.RequestProcessor.process(
RequestProcessor.java:211)
org.apache.struts.action.ActionServlet.process(
ActionServlet.java:1164)
org.apache.struts.action.ActionServlet.doGet(
ActionServlet.java:397)
javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
root cause
javax.servlet.jsp.JspException: Cannot retrieve mapping for action /SubmitValid
org.apache.struts.taglib.html.FormTag.lookup(FormTag.java:723)
org.apache.struts.taglib.html.FormTag.doStartTag(FormTag.java:419)
org.apache.jsp.pages.test_002dvalidation_jsp._jspx_meth_html_form_0(
org.apache.jsp.pages.test_002dvalidation_jsp:150)
org.apache.jsp.pages.test_002dvalidation_jsp._jspService(
org.apache.jsp.pages.test_002dvalidation_jsp:92)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:97)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
org.apache.jasper.servlet.JspServletWrapper.service(
JspServletWrapper.java:322)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:291)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:241)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
org.apache.struts.action.RequestProcessor.doForward(
RequestProcessor.java:1056)
org.apache.struts.tiles.TilesRequestProcessor.doForward(
TilesRequestProcessor.java:261)
org.apache.struts.action.RequestProcessor.internalModuleRelativeForward(
RequestProcessor.java:994)
org.apache.struts.tiles.TilesRequestProcessor.internalModuleRelativeForward(
TilesRequestProcessor.java:343)
org.apache.struts.action.RequestProcessor.processForward(
RequestProcessor.java:553)
org.apache.struts.action.RequestProcessor.process(
RequestProcessor.java:211)
org.apache.struts.action.ActionServlet.process(ActionServlet.java:1164)
org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:397)
javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
|
Struts 老手会认出这个错误消息的意义是:在 pages/validation-test.jsp 中定义了一个叫做 SubmitValid
的动作,但是在 struts-config.xml 文件中没有匹配的 action
元素。还有其他相关问题:还需要 form-bean
。下面我将处理这两个问题。
配置验证测试页面
首先来处理遗漏的 SubmitValid
动作。请把清单 10 中的条目添加到 struts-config.xml 文件:
清单 10. struts-config.xml 文件的条目
<!-- Default "Welcome" action -->
<!-- Forwards to Welcome.jsp -->
<action
path="/Welcome"
forward="/pages/Welcome.jsp"/>
<action path="/TestSimpleValidation"
forward="/pages/test-validation.jsp" />
<action path="/SubmitValid"
type="com.ibm.struts.validation.ValidationAction"
name="ValidationForm"
scope="request"
validate="true"
input="/pages/test-validation.jsp">
<forward name="success" path="/pages/success.jsp" redirect="true"/>
<forward name="failure" path="/pages/test-validation.jsp"
redirect="true" />
</action>
<!-- Other action elements -->
|
这段代码做的事情并不多;它只是把 test-validation.jsp 的表单关联到一个新类 com.ibm.struts.validation.ValidationAction
。这个类我还没有介绍;马上就要编写它的代码。剩下的内容对于任何 Struts 开发人员看起来都应当很典型。它指明了传递给动作的表单的名称、范围以及输入页面。它还开启了验证,至于原因,不言自明。
现在,在访问页面时,将得到新的错误消息(假设做了以上修改,并重新部署了应用程序):
清单 11. 新的错误消息
javax.servlet.ServletException: Cannot retrieve definition for form bean ValidationForm on action /SubmitValid
org.apache.jasper.runtime.PageContextImpl.doHandlePageException(PageContextImpl.java:848)
org.apache.jasper.runtime.PageContextImpl.handlePageException(PageContextImpl.java:781)
org.apache.jsp.pages.test_002dvalidation_jsp._jspService(org.apache.jsp.pages.test_002dvalidation_jsp:102)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:97)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:322)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:291)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:241)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
org.apache.struts.action.RequestProcessor.doForward(RequestProcessor.java:1056)
org.apache.struts.tiles.TilesRequestProcessor.doForward(TilesRequestProcessor.java:261)
org.apache.struts.action.RequestProcessor.internalModuleRelativeForward(RequestProcessor.java:994)
org.apache.struts.tiles.TilesRequestProcessor.internalModuleRelativeForward(TilesRequestProcessor.java:343)
org.apache.struts.action.RequestProcessor.processForward(RequestProcessor.java:553)
org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:211)
org.apache.struts.action.ActionServlet.process(ActionServlet.java:1164)
org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:397)
javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
|
这是一个容易修复的问题;只需添加新的 form-bean
元素即可,也是添加到 WEB-INF/struts-config.xml 文件,这个元素定义了表单的 JavaBean,如清单 12 所示:
清单 12. form-bean 元素
<form-beans>
<!-- Other form-bean listings -->
<form-bean name="ValidationForm"
type="org.apache.struts.action.DynaActionForm">
<form-property name="username" type="java.lang.String" />
<form-property name="password" type="java.lang.String" />
<form-property name="phone" type="java.lang.String" />
<form-property name="email" type="java.lang.String" />
<form-property name="url" type="java.lang.String" />
</form-bean>
</form-beans>
|
现在就得到了一个可工作的配置。重新部署这些更改,访问欢迎页面,点击链接。应当看到如图 3 所示的验证测试页面:
图 3. 现在欢迎屏幕将把您带到测试表单
这可能不是您看过的最漂亮的表单,但是对于我们的目的来说足够了。需要放一个 Action
来处理表单,剩下的就是一些验证了 —— 当然,这正是我们感兴趣的部分!
添加用于进行处理的定制动作
因为不需要考虑什么业务逻辑(只不过是个示例应用程序),所以编写验证表单的定制 Action
很容易,如清单 13 所示:
清单 13. 定制动作
package com.ibm.struts.validation;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
public class ValidationAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest req,
HttpServletResponse res)
throws Exception {
ActionForward forward = null;
// Perform some sort of business logic with the data. In this sample,
// we don't care about this; in fact, if the application got here,
// they've already passed validation, and we just need to return success
forward = mapping.findForward("success");
return forward;
}
}
|
当然,这个动作在 struts-config.xml 中用 SubmitValid
动作引用。在这个示例中,它什么也不做 —— 只是用 “success” 转发把控制传递给 Struts 控制器。这与 struts-config.xml 文件匹配并请求 /pages/success.jsp JSP,如下所示:
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<html:html locale="true">
<head>
<title>Welcome</title>
<html:base/>
</head>
<body bgcolor="white">
<h3>Validation Successful!</h3>
</body>
</html:html>
|
当然,没什么特别的内容,因为这篇教程介绍的是验证,而不是页面设计。
值得一提的是,这里还没有有验证代码!由于还没有创建定制 ActionForm
和自行实现 validate()
方法,所以 Action
对于什么是正确的一无所知,实际上,如果没有验证问题,甚至不会访问它。这意味着代码完全没有验证逻辑,而且就像在下一节中看到的,代码将保持这个样子。
实际上,这是我花了很多时间介绍应用程序设置的主要原因之一。代码、业务逻辑、甚至大多数配置都缺乏验证逻辑。所以每次修改允许的口令长度时,都不得不考虑重新部署表单和动作。实际上,只需要修改一个小小的 XML 文件就可以了。但是现在我还要向前走;在下一节,将开始查看要做 什么才能利用验证。
使用 Validator
没有 Validator 的 Struts
在讨论用 Validator 能做什么之前,先简要地描述一下没有 Validator 时验证内容必须要做什么。回忆一下前面添加到 WEB-INF/struts-config.xml 中的 form-bean
:
<form-bean name="ValidationForm"
type="org.apache.struts.action.DynaActionForm">
<form-property name="username" type="java.lang.String" />
<form-property name="password" type="java.lang.String" />
<form-property name="phone" type="java.lang.String" />
<form-property name="email" type="java.lang.String" />
<form-property name="url" type="java.lang.String" />
</form-bean>
|
如果 Validator 既没安装也不可用,还想执行服务器端验证,那么需要替换表单的类型 —— 把当前的设置 org.apache.struts.action.DynaActionForm
换成定制类型,例如 com.ibm.struts.ValidationForm
。这个类要扩展默认的 Struts 表单 org.apache.struts.action.ActionForm
。然后,要实现 validate()
方法。看起来可能像这样:
public ActionErrors validate(ActionMapping mapping, HttpServletRequest req) {
ActionErrors errors = new ActionErrors();
// Do all sorts of validation
if ((getUsername() == null) || (getUserName().length() < 1)) {
errors.add("username", new ActionMessage("validation.errors.username.required"));
} else if (getUsername().length() < 8) {
errors.add("username", new ActionMessage("validation.errors.username.too-short"));
} // ... etc.
return errors;
}
|
不仅需要对应用程序中的每个表单创建定制类,而且还会违犯在教程前面的介绍中提到过的一个原则 —— 验证是基于服务器的。每个请求都必须到达 servlet 引擎、被委托给 Struts、传递给正确的 ActionForm
、得到处理、然后再返回(返回给 Struts、再到 servlet 容器,然后到用户)。糟透了,是不是?
您可能把验证移动到 JavaScript(下一个最佳选择),但是这也是一个痛苦,至于理由已经在前面提到过。另外,JavaScript 模块(您正在模块文件中编写脚本,而不是直接写到 JSP 中,对么?)不能访问 Struts 的属性文件,这意味着添加到 MessageResources.properties 中的所有这些好的错误消息,在验证中都用不上。这样就丧失了一些 Struts 最好的特性:模块化和易于国际化。
显然,肯定有更好的方法。而且,谢天谢地,现在是时候研究 Validator 代码了。
动态验证和 Validator
如果关闭了 struts-config.xml 文件,请再次打开它。现在回到 form-bean
元素。需要修改表单类型,但不是改成定制类,而是使用 Validator 提供的类 org.apache.struts.validator.DynaValidatorForm
,如下所示:
<form-bean name="ValidationForm"
type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="username" type="java.lang.String" />
<form-property name="password" type="java.lang.String" />
<form-property name="phone" type="java.lang.String" />
<form-property name="email" type="java.lang.String" />
<form-property name="url" type="java.lang.String" />
</form-bean>
|
做这一修改,保存修改过的配置文件,并重新部署应用程序。如果进入验证表单,输入一些假值(或根本不输入值),并点击 Submit。将会看到和以前看到的一样的 success.jsp 页面。实际上,做这个修改对应用程序没有实际的改变。因为没有指定任何要应用的验证规则。但是,现在有了一个可以指定这些规则的框架 —— 根本不用做代码级或类级的修改。
在进入之前,我要先给出一些强烈的建议:在表单 bean 中应当一直 使用 DynaValidatorForm
。因为没有设置验证规则时,它的作用就像 Struts 的 DynaActionForm
一样。但是,更重要的是,以后总能在不修改表单 bean 的情况下就添加验证规则。由于这个原因(除非正在使用定制的 ActionForm
实现),我在我所有的表单 bean 中都尝试使用 DynaActionForm
。
添加验证规则
现在我们进入有趣的部分。打开 WEB-INF/validation.xml,这是我在前面提到过的两个特定于 Validator 的配置文件的第二个(第一个是 validator-rules.xml,负责指定通用验证规则)。validation.xml 文件包含表单特定字段(在 struts-config.xml 中定义)和通用验证规则(在 validator-rules.xml 中)的映射。请打开 validation.xml 并找到 formset
元素。在这个元素内,添加清单 14 中的代码(现在还没有意义,但马上会解释它):
清单 14. formset 元素的代码
<form name="ValidationForm">
<field property="username"
depends="required,minlength">
<arg0 key="prompt.username" />
<arg1 key="${var:minlength}" name="minlength"
resource="false" />
<var>
<var-name>minlength</var-name>
<var-value>6</var-value>
</var>
</field>
<field property="password"
depends="required,minlength">
<arg0 key="prompt.password" />
<arg1 key="${var:minlength}" name="minlength"
resource="false" />
<var>
<var-name>minlength</var-name>
<var-value>8</var-value>
</var>
</field>
</form>
|
首先,form
元素指明开始了一个新表单;name
属性标识这个表单。不出所料,这里的名称应当与 struts-config.xml 中的 form-bean
元素匹配。表单列出后,需要为每个想要验证的字段指定规则。
在这个示例中,我用了两个最简单、也最常见的在 username
和 password
字段上的规则。第一个规则是 required
(必需的),而且像其他规则一样,由 field
元素的 depends
属性指定。只需列出想要使用的规则,一个接着一个,用逗号分隔即可。然后,对于每个规则,要提供一些数据供 validator 使用。例如,required
规则需要一个参数;这个参数是字段的名称,在字段没有错误时,显示在生成的错误信息上。使用 arg0
元素可以提供这个名称(在这个示例中,是 prompt.username
和 prompt.password
,直接从可信任的 MessageResources.properties 文件提取)。现在开始来看这些内容是如何合在一起的吧?
除了 required
规则,我还使用 minlength
规则。(请注意这些规则是 大小写敏感的!如果录入错误,就会被忽略,会让人很郁闷。)这条规则要求的参数不止一个;它需要一个变量指明字段可以接受的最小长度。这个值通过 var
、var-name
和 var-value
元素传递给 minlength
规则。从代码中看它应当更清楚,所以我让您自己去弄清楚这里发生的事情。
最后,还需要另一个参数 —— 这次针对的是 minlength
规则可能需要生成的任意错误消息。因为 arg0
被 required
规则使用了,所以在此基础上加 1,用 arg1
向 minlength
规则提供必需的最小字符数。请注意在这里变量是如何引用的。resource="false"
语句告诉 validator 从当前文件中得到这个数据,而不是从应用程序的资源绑定(MessageResources.properties)中获得。
这里,应用程序将要求用户名和口令字段的值,还要求输入这些字段的数据有最小长度(请注意每个字段的最小长度不同)。非常漂亮!即使 validation.xml 中的规则定义非常详细,对于每个规则该做什么也非常清楚。
使用 Validator 的内置规则
Struts Validator 自带了许多有用的内置规则;可以检查数据类型(integer
、data
、byte
等等。)和数据的范围(range
)。已经看过的有 minlength
规则,还有 maxlength
规则。甚至可以用 creditCard
规则保证数字和信用卡的数字格式匹配。我要使用另两个规则 —— email
和 url
—— 来验证测试表单上的数据正确。
下面是 validation.xml 中的一些新规则:
<field property="email"
depends="email">
<arg0 key="prompt.email" />
</field>
<field property="url"
depends="url">
<arg0 key="prompt.url" />
</field>
|
第一个要求 E-Mail 字段中的数据必须是正确的地址(让 Struts 处理它不好么?)。第二个确保 URL 是正确的。
(请注意:至少在我自己的测试中,Struts 没有生成 URL 验证方法 的 JavaScript 版本。这意味着如果所有的表单字段都包含正确的数据,而独独 URL 字段不包含,那么客户端验证会通过但是服务器端验证会失败。这并不是可能发生的最坏的事情,因为 URL 字段仍然会得到验证,但是我期望在 Struts 的未来版本中,这个问题会得到纠正。在任何情况下,显示没有理由可以停用 url
规则。)
使用 mask 规则
在许多情况下文本数据需要采用特殊的格式;但是 Struts 不可能解决所有这些可能的格式:电话号码、电子邮件地址、URL、邮编、驾驶证号......这个列表在不断地增长。所以,Struts 只处理最常见的(email
和 url
),而为其他格式提供了 mask
规则。使用正则表达式,可以用 mask
规则指定自己的文本模式。
如果对于正则表达式比较熟悉,那么这就是小菜一碟。(对正则表达式的介绍超出了本教程的范围。如果需要帮助,请参阅 参考资料 获得关于正则表达式的更多内容。例如,可以这样定义 phone
字段的规则:
<field property="phone"
depends="required,mask">
<arg0 key="prompt.phone" />
<var>
<var-name>mask</var-name>
<var-value>^\(?(\d{3})\)?[-| ]?(\d{3})[-| ]?(\d{4})$</var-value>
</var>
</field>
|
这是一个使用得很普遍的模式,它允许输入各种流行的电话格式。如果使用了错误的表单,Validator 会输出关于错误数据的消息。
但是,可以容易地在多个地方使用同一个掩码。每次使用时不必重新定义,只要把掩码定义移动到 global constant 即可。请把下面的内容添加到 validation.xml 文件中的 global
元素内(靠近文件顶部):
<global>
<constant>
<constant-name>phone</constant-name>
<constant-value>^\(?(\d{3})\)?[-| ]?(\d{3})[-| ]?(\d{4})$</constant-value>
</constant>
</global>
|
现在修改 phone
字段的条目,像下面这样使用新的常数掩码值:
<field property="phone"
depends="required,mask">
<arg0 key="prompt.phone" />
<var>
<var-name>mask</var-name>
<var-value>${phone}</var-value>
</var>
</field>
|
现在在整个应用程序的验证规则中,都可以用其他形式重用这个模式。
最后,请注意,我没有提供 Validator 内置规则的完整列表。这是有意的,因为这些规则在不断地变化,随时都有对新规则的支持出现。对于最新的规则集,请查看在线的 Struts Validator 开发指南(请参阅 参考资料 获得链接)。谁知道在您阅读这篇教程的时候会有什么有趣的规则可用呢?
在 JSP 中支持验证
这些规则都不错,但是仍然有一个显著的问题:JSP 页面没有办法报告错误!例如,如果这里重新部署应用程序,将会发现用错误的值 提交验证表单不会 转向 success.jsp。这是对的,也是朝着正确方向的一步。但是,它也特别令人郁闷;表单只是在浏览器中重新出现,至于出了什么错误却没有任何指示。
请打开 pages/test-validation.jsp 来处理这个问题。首先,需要提供了一个地方,供发生错误时显示。这可以由 html:errors
元素很好地处理。请把以下内容插在 html:form
元素前:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<html:xhtml />
<html>
<head>
<title><bean:message key="valid.title" /></title>
</head>
<html:errors />
<html:form action="/SubmitValid" focus="username">
|
如果现在重新部署,并试着输入错误数据,将开始看到错误消息,如图 4 所示:
图 4. 用错误数据提交表单现在会触发错误消息
现在朝着真正坚固的验证又进了一步。但是,仍然在服务器端;在提交表单时会得到讨厌的闪烁,然后不得不等待服务器响应。这里的目标是用 JavaScript 编码的验证(或者更好一些,不必为了每个表单手工编写 JavaScript)。高兴的是,只要几个额外标记,Validator 就允许把它的服务器端代码转换成客户端 JavaScript 脚本:
</html:form>
<html:javascript formName="ValidationForm" cdata="false" />
</body>
|
不是很多哦!只有一行代码,但是它告诉 Struts 插入所有支持表单客户端验证的 JavaScript。还请注意非常重要的 cdata
属性。必须 把它设置成 false
,否则客户端验证不会工作。如果把它设置成 true
(默认设置),那么 Struts 生成的 HTML 会把 JavaScript 放在 <![CDATA[
和 ]]>
标记内。由于一些我不太清楚的原因,多数现代浏览器(Mozilla Firefox 和 Safari 在其中最出名)会忽略这样包含的 JavaScript,所以客户端验证不会执行。
在这一阶段,我要指出,即使忘记了 cdata=false
这一部分,仍然可以得到验证,只不过是在服务器端而已。在任何情况下,Validator 在服务器端都会验证数据,所以如果忘记了这个属性、甚至关闭 JavaScript 或跳过它,也都会得到一个备份。使用 Validator 有一个很好的副作用 —— 它会尝试并捕获每个可能的错误。
(说明:如果按照作者的说法,得不到下面的测试结果,还需要修改一个地方)
修改文件test-validation.jsp,增加黄色背景部分
<html:form action="/SubmitValid" onsubmit="validateValidationForm(this)" focus="username"> 测试完成的应用程序
启用 JavaScript 后,最后一次重新部署应用程序,并输入各种错误数据。单击 Submit,将得到一个精彩的客户端错误列表(如图 5、6、7 所示)。这可能是您惟一的一次因为看到错误消息而兴奋!
图 5. 口令太短
图 6. 电话号码格式不对
图 7. 错误的电子邮件确实被当作错误
这些消息是顺序处理的,主要由 Validator 引擎决定一次处理多少消息。换句话说,有可能得到一条消息指示多个为空的字段必须填写。但是,单击 OK 并纠正这个错误之后,还会出现不同的错误(例如电话号码格式不对)。Validator 总是只呈现一个对话框,但是这个对话框可以包含多个错误。虽然 Validator 有选择地呈现这些错误,但是它还是会确保在允许用户继续操作之前,要满足所有验证规则。
结束语
结束语
这篇教程看起来可能更像一大堆配置而不是编程。我更多是一个编码人员,但是有时您不得不花大量的配置时间,以便可以少些编码时间。没有 Validator 或者类似的组件,就不得不编写 Java 或 JavaScript 代码,对 Struts 表单上的每个输入框手工进行验证。即使热爱编码的人也恨透了编写验证句柄,特别是憎恨一遍又一遍地编写。
现在应当已经从 Struts 示例应用程序和本教程介绍的验证规则构建了一个可工作的 Validator 设置和一个好的测试环境。当我在新机器上安装 Tomcat、Struts 和 Validator 时,我经常使用这个正确的设置;它可以帮我免除跟踪错误的麻烦。另外,对于您现在和未来的 Web 应用程序,也可以使用这个配置。
通过本教程,您应当已经对 Struts 和 Validator 组件真正有了些熟悉,特别是对它们的配置文件。甚至您现在还没有使用 Validator 组件,也应当有助于您掌握 Struts 安装。
不论您如何使用本教程,我都希望它会把您的 Struts 开发(和验证)带上一个层次!希望您喜欢!
下载
描述 | 文件名称 | 文件大小 | 下载方法 |
---|
Sample code | j-strutsvalcode.zip | 2 MB | FTP |
参考资料
学习关于作者
|
| Brett McLaughlin 从 Logo 时代(还记得那个小三角么?)就从事计算机工作。最近几年,他已经成为 Java 技术和 XML 社区最著名的作者和程序员之一。他为 Nextel Communications 工作过,实现了复杂的企业系统;在 Lutris Technologies 工作,实际编写了应用服务器;最近是在 O'Reilly Media, Inc. 工作,在这里他继续编写和编辑这方面的书籍。他的最新大作 Java 1.5 Tiger: A Developer's Notebook是关于最新版本 Java 技术的第一本图书,他经典的 Java and XML保持着在 Java 语言中使用 XML 技术的权威作品之一的地位。 |