11. 示例 09 - 整数的转换与验证
接下来我们将通过一系列示例,探讨表单参数的转换与验证。具体问题如下:为了处理 [http://machine:port/.../Action] 格式的 URL,[FilterDispatcher] 控制器会实例化实现所请求操作的类,并执行该类的一个方法——默认情况下是名为 execute 的方法。对该 execute 方法的调用会经过一系列拦截器:
![]() |
拦截器列表定义在 [struts2-core.jar] 归档文件根目录下的 [struts-default.xml] 文件中。其中定义的拦截器列表如下:
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="debugging"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*,^struts\..*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
</interceptor-stack>
在拦截器中,有一个拦截器负责将请求携带的参数值以 parami=values 的形式注入到 Action 中。我们知道,如果 Action 存在 setParami 方法,这些值就会通过该方法注入到 Action 的 parami 字段中。否则,不会进行注入,也不会报告错误。
字符串 parami=values 是一个字符串。到目前为止,值的注入都是针对类型为 String 的 parami 字段进行的:
将字符串 valeuri 注入为 *parami* 的值时没有问题。如果 parami 不是 String 类型,则必须将 valeuri 转换为 parami 的类型 Ti。这就是转换问题。例如,我们可能希望年龄是一个整数,并在操作中写道:
此外,我们可能希望将年龄限制在 1 到 150 之间。这便涉及一个有效性验证问题。parami 参数虽然可以转换为正确的类型,但其值未必有效。因此需要完成两个步骤。回到请求处理图:
![]() |
将分别由两个拦截器处理参数的转换和验证。如果任一步骤失败,请求将不会继续执行到动作(如上图红色路径所示)。提交了错误参数的表单将重新显示,并附带错误信息。
参与参数转换和验证的拦截器分别是前面拦截器列表第19行和第20行的conversionError和validation拦截器。请注意第20至22行:如果被调用的方法是input、back、cancel或browse方法之一,则不会应用validation拦截器。我们稍后将利用这一特性。
我们将首先探讨整数的转换与验证。由于该验证涉及多个要素,我们将花些时间讲解这个第一个示例。一旦理解了这些内容,后续示例的讲解就会更加迅速。
11.1. 表单
![]() |
- 在 [1] 中,输入表单
- 在[2]中,未输入任何值时的验证结果
11.2. NetBeans 项目
NetBeans 项目如下:
![]() |
- 在 [1] 中,应用程序的三个视图
- 在 [2] 中,源代码、国际化消息文件以及 Struts 配置文件。
11.3. Struts 配置
该应用程序通过 [struts.xml] 和 [example.xml] 文件进行配置。
[struts.xml] 文件内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.custom.i18n.resources" value="messages" />
<include file="example/example.xml"/>
<package name="default" namespace="/" extends="struts-default">
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Accueil</param>
<param name="namespace">/example</param>
</result>
</action>
</package>
</struts>
第 12–18 行将 [/example/Home] 动作定义为用户未指定动作时的默认动作。
[example.xml] 文件内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="example" namespace="/example" extends="struts-default">
<action name="Accueil">
<result name="success">/example/Accueil.jsp</result>
</action>
<action name="FormInt" class="example.FormInt">
<result name="input">/example/FormInt.jsp</result>
<result name="cancel" type="redirect">/example/Accueil.jsp</result>
<result name="success">/example/ConfirmationFormInt.jsp</result>
</action>
</package>
</struts>
- 第 8–10 行:[Home] 操作显示 [Home.jsp] 视图
- 第 11 行:[FormInt] 操作默认会调用 [example.FormInt] 类的 execute 方法。我们将看到另外两个方法也会被调用:input 和 cancel 方法。这些方法随后将在请求参数中指定。
- 第 12 行:input 键将显示 [FormInt.jsp] 视图(第 5 行)。该视图即表单视图。
- 第 13 行:cancel 键将由与 [Cancel] 链接关联的 cancel 方法返回。随后渲染的视图将是 [Home.jsp] 视图(通过 type=redirect 进行重定向)。
- 第 14 行:`success` 键由 [FormInt] 动作的 `execute` 方法返回。如果请求到达了 `execute` 方法,则意味着它已成功通过了所有拦截器,特别是那些验证参数有效性的拦截器。随后,`execute` 方法仅返回 `success` 键,这将显示确认视图 [ConfirmationInt.jsp]。
11.4. 消息文件
[messages.properties] 文件内容如下:
Accueil.titre=Accueil
Accueil.message=Struts 2 - Conversions et validations
Accueil.FormInt=Saisie de nombres entiers
Form.titre=Conversions et validations
FormInt.message=Struts 2 - Conversion et validation de nombres entiers
Form.submitText=Valider
Form.cancelText=Annuler
Form.clearModel=Raz mod\u00e8le
Confirmation.titre=Confirmation
Confirmation.message=Confirmation des valeurs saisies
Confirmation.champ=champ
Confirmation.valeur=valeur
Confirmation.lien=Formulaire de test
除了此文件外,视图还使用以下 [FormInt.properties] 文件:
int1.prompt=1-Nombre entier positif de deux chiffres
int1.error=Tapez un nombre entier positif de deux chiffres
int2.prompt=2-Nombre entier
int2.error=Tapez un nombre entier
int3.prompt=3-Nombre entier >=-1
int3.error=Tapez un nombre entier >=-1
int4.prompt=4-Nombre entier <=10
int4.error=Tapez un nombre entier <=10
int5.prompt=5-Nombre entier dans l''intervalle [1,10]
int5.error=Tapez un nombre entier dans l''intervalle [1,10]
int6.prompt=6-Nombre entier dans l''intervalle [2,20]
int6.error=Tapez un nombre entier dans l''intervalle [2,20]
[FormInt.properties] 文件仅在生成视图的操作为 [FormInt] 操作时使用。这是在消息文件过大时将其拆分的一种方式。Action 操作的消息在 [Action.properties] 文件中进行本地化。
11.5. 视图与操作
接下来我们将介绍该应用程序的视图和操作。根据应用程序配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="example" namespace="/example" extends="struts-default">
<action name="Accueil">
<result name="success">/example/Accueil.jsp</result>
</action>
<action name="FormInt" class="example.FormInt">
<result name="input">/example/FormInt.jsp</result>
<result name="cancel" type="redirect">/example/Accueil.jsp</result>
<result name="success">/example/ConfirmationFormInt.jsp</result>
</action>
</package>
</struts>
我们可以看到,这里有三个视图 [Home.jsp、FormInt.jsp、ConfirmationFormInt.jsp] 和两个动作 [Home、FormInt]。
11.5.1. Home.jsp
视图 [Home.jsp] 如下所示:

其代码如下:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title><s:text name="Accueil.titre"/></title>
<s:head/>
</head>
<body background="<s:url value="/ressources/standard.jpg"/>">
<h2><s:text name="Accueil.message"/></h2>
<ul>
<li>
<s:url id="url" action="FormInt!input"/>
<s:a href="%{url}"><s:text name="Accueil.FormInt"/></s:a>
</li>
</ul>
</body>
</html>
第 14 行中的链接会生成以下 HTML 代码:
<a href="<a href="view-source:http://localhost:8084/exemple-09/example/FormInt.action">/exemple-09/example/FormInt!input.action</a>">Saisie de nombres entiers</a>
因此,这是一个指向 [FormInt] 操作的链接,该操作在 [example.xml] 中配置如下:
<action name="FormInt" class="example.FormInt">
<result name="input">/example/FormInt.jsp</result>
<result name="cancel" type="redirect">/example/Accueil.jsp</result>
<result name="success">/example/ConfirmationFormInt.jsp</result>
</action>
点击该链接将实例化 [example.FormInt] 类并执行其 input 方法。由于该方法不存在,因此将执行父类 ActionSupport 的 input 方法。该方法除了返回 input 键外不执行任何操作。因此,将显示 [/example/FormInt.jsp] 视图。
此外,input 方法是验证拦截器忽略的方法之一:
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
因此,将不会进行参数验证。这一点很重要,因为这里没有参数,而我们稍后会看到,验证规则要求必须存在六个参数。
11.5.2. [FormInt] 操作
[FormInt] 操作与以下 [FormInt] 类相关联:
package example;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
import org.apache.struts2.interceptor.validation.SkipValidation;
public class FormInt extends ActionSupport implements ModelDriven, SessionAware {
// constructor without parameters
public FormInt() {
}
// action model
public Object getModel() {
if (session.get("model") == null) {
session.put("model", new FormIntModel());
}
return session.get("model");
}
public String cancel() {
// cleaning the model
((FormIntModel) getModel()).clearModel();
// result
return "cancel";
}
@SkipValidation
public String clearModel() {
// close to the model
((FormIntModel) getModel()).clearModel();
// result
return INPUT;
}
// SessionAware
private Map<String, Object> session;
public void setSession(Map<String, Object> session) {
this.session = session;
}
// validation
@Override
public void validate() {
// valid int6 input?
if (getFieldErrors().get("int6") == null) {
int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
if (int6 < 2 || int6 > 20) {
addFieldError("int6", getText("int6.error"));
}
}
}
}
我们将根据需要对这段代码进行注释。目前:
- 第 9 行,[FormInt] 类实现了两个接口:
- ModelDriven 类仅有一个方法,即第 16 行中的 getModel
- SessionAware,该类仅有一个方法,即第 41 行的 setSession
- 第 16–21 行:ModelDriven 接口的实现。请注意,该接口允许将视图的模型委托给外部类,在本例中即为下述 [FormIntModel] 类:
package example;
public class FormIntModel {
// constructor without parameters
public FormIntModel() {
}
// form fields
private String int1;
private Integer int2;
private Integer int3;
private Integer int4;
private Integer int5;
private String int6;
// raz model
public void clearModel(){
int1=null;
int2=null;
int3=null;
int4=null;
int5=null;
int6=null;
}
// getters and setters
...
}
[FormIntModel] 模型有六个字段,分别对应 [FormInt.jsp] 视图中的六个输入字段。这六个字段将接收提交的值。其中四个字段的类型为 Integer。因此,对于这四个字段,将出现将 String 转换为 Integer 的问题。clearModel 方法允许您重置模型。
让我们回到 [FormInt] 动作的 getModel 方法:
// action model
public Object getModel() {
if (session.get("model") == null) {
session.put("model", new FormIntModel());
}
return session.get("model");
}
- 第 3–5 行:从会话中检索模型。如果会话中不存在该模型,则创建一个模型实例并将其放入会话中。
- 第 6 行:虽然每次向该操作发起新请求时都会创建一个操作实例,但其模型仍保留在会话中。
我们可以看到,该类并未定义输入方法,但其父类定义了一个返回输入键的方法。调用该方法将显示 [FormInt.jsp] 视图,我们现在将展示该视图。
11.5.3. [FormInt.jsp] 视图
[FormInt.jsp] 视图如下:
![]() |
- [1] 中的空白表单
- 在 [2] 中,参数验证失败后的表单。
其代码如下:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title><s:text name="Form.titre"/></title>
<s:head/>
</head>
<body background="<s:url value="/ressources/standard.jpg"/>">
<h2><s:text name="FormInt.message"/></h2>
<s:form name="formulaire" action="FormInt">
<s:textfield name="int1" key="int1.prompt"/>
<s:textfield name="int2" key="int2.prompt"/>
<s:textfield name="int3" key="int3.prompt"/>
<s:textfield name="int4" key="int4.prompt"/>
<s:textfield name="int5" key="int5.prompt"/>
<s:textfield name="int6" key="int6.prompt"/>
<s:submit key="Form.submitText" method="execute"/>
</s:form>
<br/>
<s:url id="url" action="FormInt" method="cancel"/>
<s:a href="%{url}"><s:text name="Form.cancelText"/></s:a>
<br/>
<s:url id="url" action="FormInt" method="clearModel"/>
<s:a href="%{url}"><s:text name="Form.clearModel"/></s:a>
</body>
</html>
- 第 12–17 行:六个输入字段对应于 [FormInt] 操作的 [FormIntModel] 模型中的六个字段。当视图显示时,输入字段的 value 属性将用于显示这些字段的值。如果缺少 value 属性,则使用 name 属性。
- 第 12 行:该输入字段与操作的 int1 字段相关联(name),或者如果该操作实现了 ModelDriven 接口,则与该操作的模型中的 int1 字段相关联。本例中即为这种情况。其他所有字段也是如此。
- 第 18 行:[Submit] 按钮将表单数据提交至第 11 行定义的 [FormInt] 操作。其 execute 方法将被执行。
- 第 21–22 行:[Cancel] 链接执行 [FormInt.cancel] 方法。
- 第 24–25 行:[Clear Model] 链接执行 [FormInt.clearModel] 方法。
11.5.4. [ConfirmationFormInt.jsp] 视图
![]() |
当 [FormInt.jsp] 表单中的所有输入均有效时,该视图将被显示。
- 在 [1] 中,提交了有效值
- 在[2]中,确认页面
[ConfirmationInt.jsp]视图的代码如下:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title><s:text name="Confirmation.titre"/></title>
<s:head/>
</head>
<body background="<s:url value="/ressources/standard.jpg"/>">
<h2><s:text name="Confirmation.message"/></h2>
<table border="1">
<tr>
<th><s:text name="Confirmation.champ"/></th>
<th><s:text name="Confirmation.valeur"/></th>
</tr>
<tr>
<td><s:text name="int1.prompt"/></td>
<td><s:property value="int1"/></td>
</tr>
<tr>
<td><s:text name="int2.prompt"/></td>
<td><s:property value="int2"/></td>
</tr>
<tr>
<td><s:text name="int3.prompt"/></td>
<td><s:property value="int3"/></td>
</tr>
<tr>
<td><s:text name="int4.prompt"/></td>
<td><s:property value="int4"/></td>
</tr>
<tr>
<td><s:text name="int5.prompt"/></td>
<td><s:property value="int5"/></td>
</tr>
<tr>
<td><s:text name="int6.prompt"/></td>
<td><s:property value="int6"/></td>
</tr>
</table>
<br/>
<s:url id="url" action="FormInt" method="input"/>
<s:a href="%{url}"><s:text name="Confirmation.lien"/></s:a>
</body>
</html>
要理解这段代码,请记住视图是在 [FormInt] 类实例化之后显示的。因此,该类及其模型 [FormIntModel] 的字段均可被视图访问。
- 第 16–38 行:显示六个字段的值
- 第 42–43 行:指向 [FormInt] 操作的链接。为此链接生成的代码如下:
<a href="/exemple-09/example/FormInt!input.action">Formulaire de test</a>
该链接的具体 URL 表明 [FormInt] 操作的输入方法必须处理此请求。回顾 [example.xml] 中 [FormInt] 操作的配置:
<action name="FormInt" class="example.FormInt">
<result name="input">/example/FormInt.jsp</result>
<result name="cancel" type="redirect">/example/Accueil.jsp</result>
<result name="success">/example/ConfirmationFormInt.jsp</result>
</action>
[FormInt] 类的 input 方法将继承自其父类 ActionSupport。在拦截器执行完毕后,[FormInt] 类的 input 方法才会被执行
![]() |
我们知道,调用 input 方法会被验证拦截器忽略。因此,将不会进行验证。
显示 [FormInt.jsp] 视图:
![]() |
在 [2] 中,输入字段恢复为默认值。这看似正常,实则不然。由于调用了 [FormInt] 操作,因此关联的 [FormInt] 类已被实例化。由于该类实现了 ModelDriven 接口,因此其 getModel 方法被调用:
// action model
public Object getModel() {
if (session.get("model") == null) {
session.put("model", new FormIntModel());
}
return session.get("model");
}
我们可以看到,该操作的模型是从会话中检索出来的。在上一步中,该模型已根据提交的值进行了更新。因此,我们检索到了这些值。如果我们没有将会话中的模型存入会话,那么在 [FormInt.jsp] 视图中就会出现六个空字段。
11.5.5. [FormInt!clearModel] 操作
[FormInt!clearModel] 操作由点击 [Clear Model] 链接触发:
![]() |
- [1] 中的表单,显示错误输入后的状态
- [2] 所示为点击 [清除模型] 链接后的表单。
[FormInt.clearModel] 方法如下:
@SkipValidation
public String clearModel() {
// close to the model
((FormIntModel) getModel()).clearModel();
// result
return INPUT;
}
- 第 1 行:无需执行任何验证。我们使用 @SkipValidation 注解来表示这一点。因此,验证拦截器将不会执行任何验证。
- 第 4 行:执行 [FormIntModel].clearModel 方法。我们之前已经遇到过该方法。它将模型的六个字段重置为 null。
- 第 7 行:该方法返回输入键。
回到 [FormInt] 操作的配置:
<action name="FormInt" class="example.FormInt">
<result name="input">/example/FormInt.jsp</result>
<result name="cancel" type="redirect">/example/Accueil.jsp</result>
<result name="success">/example/ConfirmationFormInt.jsp</result>
</action>
我们可以看到,点击该输入键将显示 [FormInt.jsp] 视图。该视图显示了模型的六个字段。由于这些字段均为空,因此视图中显示了六个空字段 [2]。
11.5.6. [FormInt!cancel] 操作
点击 [Cancel] 链接将触发 [FormInt!cancel] 操作:
![]() |
- [1] 中的图示为输入错误后的表单
- [2] 中,点击 [取消] 链接后的主页。
[FormInt.cancel] 方法如下:
public String cancel() {
// cleaning the model
((FormIntModel) getModel()).clearModel();
// result
return "cancel";
}
- 第 1 行:请注意,该方法前未添加 SkipValidation 注解。不过,我们确实不想执行验证。cancel 方法是验证拦截器忽略的四个方法(input、back、cancel、browse)之一,因此无需添加 SkipValidation 注解。
- 第 3 行:这清空了模型
- 第 5 行:返回 cancel 键
回到 [FormInt] 操作的配置:
<action name="FormInt" class="example.FormInt">
<result name="input">/example/FormInt.jsp</result>
<result name="cancel" type="redirect">/example/Accueil.jsp</result>
<result name="success">/example/ConfirmationFormInt.jsp</result>
</action>
我们可以看到,“cancel”键将在客户端重定向后显示 [Home.jsp] 视图。这在视图 [2] 中有所展示。
11.6. 验证过程
接下来,我们将处理与模型中以下六个字段相关的六个输入字段的验证:
// champs du formulaire
private String int1;
private Integer int2;
private Integer int3;
private Integer int4;
private Integer int5;
private String int6;
每次实例化 [FormInt] 类时,如果执行的方法未被验证拦截器忽略,则会进行此验证。该验证由以下内容控制:
- [FormInt-validation.xml] 文件(如果它与 [FormInt] 类位于同一文件夹中)
- [FormInt.validate] 方法(如果存在)。
![]() |
- 在 [1] 中:验证过程所需的 [xwork-validator-1.0.2.dtd] 文件
- 在 [2] 中:位于 [FormInt] 类同一文件夹中的 [FormInt-validation.xml] 文件
[FormInt-validation.xml] 文件内容如下:
<!--
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
-->
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://localhost:8084/exemple-09/example/xwork-validator-1.0.2.dtd">
<validators>
<field name="int1" >
<field-validator type="requiredstring" short-circuit="true">
<message key="int1.error"/>
</field-validator>
<field-validator type="regex" short-circuit="true">
<param name="expression">^\d{2}$</param>
<param name="trim">true</param>
<message key="int1.error"/>
</field-validator>
</field>
<field name="int2" >
...
</field>
...
</validators>
- 在 [3] 中,验证文件的 DTD(文档类型定义)URL。该 URL 必须可访问;否则,将不会使用该验证文件。
- 在 [7] 中,应用程序所用 DTD 的 URL。我们已将 DTD 文件放置在 example-09 项目 [1] 的 [example] 文件夹中,以便即使没有互联网连接也能使用。
- 第 11–20 行:设置与模型中 int1 字段关联的 int1 参数的验证条件。
表单中名为 int1 的标签如下:
<s:textfield name="int1" key="int1.prompt" />
模型中的 int1 字段声明如下:
private String int1;
- 第 12–14 行:验证 int1 参数是否存在(不为空)且长度不为零。若不满足此条件,则该输入字段会关联一条错误消息。该字段在 [FormInt.properties] 中定义如下:
int1.error=Tapez un nombre entier positif de deux chiffres
若出现错误,int1 参数的验证过程将终止(short-circuit=true)。
- 第 15–19 行:使用正则表达式检查 int1 参数的有效性。
- 第 16 行:正则表达式,在此情况下为两个数字,前后无任何字符。
- 第 17 行:在与正则表达式进行比较之前,将删除 int1 参数的首尾空格。
- 第 18 行:错误消息(如有)。它与前一个验证器的错误消息相同。
让我们看看它是如何工作的:
![]() |
- 在 [1] 中,int1 字段的输入不正确
- 在 [2] 中,页面返回:
- 显示了 int1.error 键的错误信息。该信息以红色显示。
- 该错误字段的标签也显示为红色。
- 错误的输入内容再次显示。必须预见到这一点,因为这未必是默认行为。
我们已经看到,如果请求成功通过了所有拦截器(特别是验证拦截器),表单验证会触发 [FormInt].execute 方法的执行:
![]() |
- 如果请求到达了操作的 execute 方法,它会像我们之前看到的那样,将 success 键返回给控制器。
- 如果验证拦截器因待验证的参数无效而阻止了请求,则会将 `input` 键返回给控制器。
由于 [FormInt] 操作配置如下:
<action name="FormInt" class="example.FormInt">
<result name="input">/example/FormInt.jsp</result>
<result name="cancel" type="redirect">/example/Accueil.jsp</result>
<result name="success">/example/ConfirmationFormInt.jsp</result>
</action>
当出现验证错误时,将显示 [FormInt.jsp] 视图,即表单。Struts 标签的设计旨在显示与其相关的任何错误消息。因此,我们将看到 [FormInt.jsp] 视图,其中各个字段都附有错误消息。这在视图 [2] 中有所展示。
现在让我们来检查 int2 字段的验证,该字段在模型中声明如下:
private Integer int2;
[FormInt-validation.xml] 中 int2 字段的验证规则如下:
<field name="int2" >
<field-validator type="required" short-circuit="true">
<message key="int2.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="int2.error"/>
</field-validator>
</field>
- 第 2–4 行:验证 int2 参数是否存在。
- 第 5–7 行:验证字符串到整数的转换是否可行
- 第 3、6 行:int2.error 键的错误消息如下:
int2.error=Tapez un nombre entier
[FormInt-validation.xml] 文件中模型的 Integer 字段 int3 的验证规则如下:
<field name="int3" >
<field-validator type="required" short-circuit="true">
<message key="int3.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="int2.error"/>
</field-validator>
<field-validator type="int" short-circuit="true">
<param name="min">-1</param>
<message key="int3.error"/>
</field-validator>
</field>
- 第 8–11 行:验证 int3 字段是否为大于等于 -1 的整数
- 第 3、7 行:int3.error 键的错误信息如下:
int3.error=Tapez un nombre entier >=-1
[FormInt-validation.xml] 模型中整数字段 int4 的验证规则如下:
<field name="int4" >
<field-validator type="required" short-circuit="true">
<message key="int4.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="int2.error"/>
</field-validator>
<field-validator type="int" short-circuit="true">
<param name="max">10</param>
<message key="int4.error"/>
</field-validator>
</field>
- 第 8-11 行:验证该值是否为小于等于 10 的整数
- 第 3、7 行:int4.error 键的错误消息如下:
int4.error=Tapez un nombre entier <=10
[FormInt-validation.xml] 文件中模型的整数字段 int5 的验证规则如下:
<field name="int5" >
<field-validator type="required" short-circuit="true">
<message key="int5.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="int2.error"/>
</field-validator>
<field-validator type="int" short-circuit="true">
<param name="min">1</param>
<param name="max">10</param>
<message key="int5.error"/>
</field-validator>
</field>
- 第 5–9 行:验证该值是否为 [1, 10] 范围内的整数。
- 第 3、8 行:int5.error 键的错误消息如下:
int5.error=Tapez un nombre entier dans l''intervalle [1,10]
[FormInt-validation.xml] 中模型的 String int6 字段的验证规则如下:
<field name="int6" >
<field-validator type="requiredstring" short-circuit="true">
<message key="int6.error"/>
</field-validator>
<field-validator type="regex" short-circuit="true">
<param name="expression">^\d{1,2}$</param>
<param name="trim">true</param>
<message key="int6.error"/>
</field-validator>
</field>
- 第 5–9 行:验证 int6 是否为 2 位字符串。
- 第 3、8 行:int6.error 键的错误消息如下:
int6.error=Tapez un nombre entier dans l''intervalle [2,20]
之前的验证并未检查 int6 参数是否为 [2,20] 范围内的整数。此检查在 [FormInt].validate 方法中进行,该方法在处理完 [FormInt-validation.xml] 文件后执行。该方法如下:
// validation
@Override
public void validate() {
// valid int6 input?
if (getFieldErrors().get("int6") == null) {
int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
if (int6 < 2 || int6 > 20) {
addFieldError("int6", getText("int6.error"));
}
}
}
- 第 5 行:我们检查 int6 字段是否存在任何错误。如果存在,则在此处停止。
- 第 6 行:如果没有错误,则从模型中获取 int6 字符串字段并将其转换为整数。
- 第 7 行:我们验证检索到的整数是否在 [2,20] 范围内。
- 第 8 行:若不满足此条件,则向 int6 字段附加一条错误消息。该错误消息通过键值 int6.error 从消息文件中获取。
如果在此验证过程结束时发现错误,则终止对 [FormInt].execute 方法的调用,并将输入键返回给 Struts 控制器。
![]() |
11.7. 最终细节
我们已经了解了几种输入整数的方法。它们并非完全等同。例如,请看 int5 和 int6 这两个输入字段:
在 [FormInt.jsp] 视图中,它们的声明如下:
<s:textfield name="int5" key="int5.prompt"/>
<s:textfield name="int6" key="int6.prompt"/>
它们的模型在 [FormIntModel.java] 中声明如下:
private Integer int5;
private String int6;
int5 字段的类型为 Integer,而 int6 字段的类型为 String。它们的验证规则不同:
<field name="int5" >
<field-validator type="required" short-circuit="true">
<message key="int5.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="int2.error"/>
</field-validator>
<field-validator type="int" short-circuit="true">
<param name="min">1</param>
<param name="max">10</param>
<message key="int5.error"/>
</field-validator>
</field>
<field name="int6" >
<field-validator type="requiredstring" short-circuit="true">
<message key="int6.error"/>
</field-validator>
<field-validator type="regex" short-circuit="true">
<param name="expression">^\d{1,2}$</param>
<param name="trim">true</param>
<message key="int6.error"/>
</field-validator>
</field>
int6 字段的验证由 [FormInt] 操作的 validate 方法执行:
public void validate() {
// valid int6 input?
if (getFieldErrors().get("int6") == null) {
int int6 = Integer.parseInt(((FormIntModel) getModel()).getInt6());
if (int6 < 2 || int6 > 20) {
addFieldError("int6", getText("int6.error"));
}
}
尽管表达方式不同,这两条验证规则都旨在验证输入的值是否为指定范围内的整数。然而,如下图所示,int5 和 int6 字段在运行时的行为有所不同:
![]() |
- 在 [1] 中,两个字段均输入了相同的错误值
- 在 [2] 中,返回了错误页面。这两个字段显示了不同的错误信息。
- 在 [3] 中,int5 字段因使用英文显示而出现不必要的提示信息。这是由于字符串转整数操作失败所致。Apache 日志中也记录了异常:
奇怪的是,Struts 试图查找方法 FormIntModel.setInt5(String value),但未能找到。
该错误消息的关键在于 xwork.default.invalid.fieldvalue。要将其翻译成法语,只需将法语文本与该键关联即可。因此,我们在 [messages.properties] 文件中添加以下行:
...
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".
11.8. 结论
至此,我们对这个首个专门用于参数验证的应用程序的研究告一段落。该应用程序的讲解较为复杂。接下来我们将探讨类似的应用程序。因此,我们将仅就变化之处进行说明。














