13. 示例 10 – 实数的转换与验证
新应用程序支持实数输入:
![]() |
- 在 [1] 中,输入表单
- 转换为 [2],返回的响应
该应用程序的运行方式与整数输入类似,因此我们仅对不同之处进行说明。
13.1. NetBeans 项目
NetBeans 项目如下:
![]() |
- 在 [1] 中,应用程序的视图
- [Accueil.jsp]:首页
- [FormDouble.jsp]:输入表单
- [ConfirmationDouble.jsp]:确认页面
- 转换为 [2],消息文件 [messages.properties] 以及 Struts 的主配置文件
- 在 [3] 中:
- [FormDouble.java]:显示并处理表单的操作
- [FormDouble-validation.xml]:[FormDouble] 动作的验证规则。该文件根据刚才介绍的方法,将这些验证任务委托给模型。
- [FormDoubleModel]:动作 [FormDouble] 的模型
- [FormDoubleModel-validation.xml]:模型的验证规则
- [FormDoubleModel.properties]:模型的消息文件
- [example.xml]:Struts 的辅助配置文件
13.2. 项目配置
该项目主要通过以下文件进行配置:[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="FormDouble" class="example.FormDouble">
<result name="input">/example/FormDouble.jsp</result>
<result name="cancel" type="redirect">/example/Accueil.jsp</result>
<result name="success">/example/ConfirmationFormDouble.jsp</result>
</action>
</package>
</struts>
它与之前研究过的整数输入配置文件类似。
13.3. 消息文件
[messages.properties]文件内容如下:
Accueil.titre=Accueil
Accueil.message=Struts 2 - Conversions et validations
Accueil.FormDouble=Saisie de nombres r\u00e9els
Form.titre=Conversions et validations
FormDouble.message=Struts 2 - Conversion et validation de nombres r\u00e9els
FormDouble.conseil=Tapez les nombres r\u00e9els avec une virgule comme 10,7
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
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".
文件 [FormDoubleModel.properties] 如下:
double.format={0,number}
double1.prompt=1-Nombre r\u00E9el
double1.error=Tapez un nombre r\u00E9el
double2.prompt=2-Nombre r\u00E9el
double2.error=Tapez un nombre r\u00E9el
double3.prompt=3-Nombre r\u00E9el >=2.64
double3.error=Tapez un nombre r\u00E9el >=2.64
double4.prompt=4-Nombre r\u00E9el <8.32
double4.error=Tapez un nombre r\u00E9el <8.32
double5.prompt=5-Nombre r\u00E9el dans l''intervalle [2.64,8.32[
double5.error=Tapez un nombre r\u00E9el dans l''intervalle [2.64,8.32[
double6.prompt=6-Nombre r\u00E9el dans l''intervalle [2.64,8.32]
double6.error=Tapez un nombre r\u00E9el dans l''intervalle [2.64,8.32]
第 1 行起着重要作用。我们稍后会再讨论这一点。
13.4. 输入表单
视图 [FormDouble.jsp] 如下所示:
<%@ 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="FormDouble.message"/></h2>
<h4><s:text name="FormDouble.conseil"/></h4>
<s:form name="formulaire" action="FormDouble">
<s:textfield name="double1" key="double1.prompt"/>
<s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' :getText('double.format',{double2})}"/>
<s:textfield name="double3" key="double3.prompt" value="%{#parameters['double3']!=null ? #parameters['double3'] : double3==null ? '' :getText('double.format',{double3})}"/>
<s:textfield name="double4" key="double4.prompt" value="%{#parameters['double4']!=null ? #parameters['double4'] : double4==null ? '' :getText('double.format',{double4})}"/>
<s:textfield name="double5" key="double5.prompt" value="%{#parameters['double5']!=null ? #parameters['double5'] : double5==null ? '' :getText('double.format',{double5})}"/>
<s:textfield name="double6" key="double6.prompt"/>
<s:submit key="Form.submitText" method="execute"/>
</s:form>
<br/>
<s:url id="url" action="FormDouble" method="cancel"/>
<s:a href="%{url}"><s:text name="Form.cancelText"/></s:a>
<br/>
<s:url id="url" action="FormDouble" method="clearModel"/>
<s:a href="%{url}"><s:text name="Form.clearModel"/></s:a>
</body>
</html>
第13至18行是六个实数输入字段。字段double2至double5具有一个复杂的value属性。通常,这六个输入字段应如下所示:
<s:textfield name="double1" key="double1.prompt"/>
<s:textfield name="double2" key="double2.prompt"/>
<s:textfield name="double3" key="double3.prompt"/>
<s:textfield name="double4" key="double4.prompt"/>
<s:textfield name="double5" key="double5.prompt"/>
<s:textfield name="double6" key="double6.prompt"/>
为解决测试中遇到的一些问题,不得不增加复杂度。目前,读者可以忽略这种复杂性。我们将在后续内容中进行说明。
13.5. 确认视图
确认视图 [ConfirmationFormDouble.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="double1.prompt"/></td>
<td><s:text name="double1"/></td>
</tr>
<tr>
<td><s:text name="double2.prompt"/></td>
<td>
<s:text name="double.format">
<s:param value="double2"/>
</s:text>
</td>
</tr>
<tr>
<td><s:text name="double3.prompt"/></td>
<td>
<s:text name="double.format">
<s:param value="double3"/>
</s:text>
</td>
</tr>
<tr>
<td><s:text name="double4.prompt"/></td>
<td>
<s:text name="double.format">
<s:param value="double4"/>
</s:text>
</td>
</tr>
<tr>
<td><s:text name="double5.prompt"/></td>
<td>
<s:text name="double.format">
<s:param value="double5"/>
</s:text>
</td>
</tr>
<tr>
<td><s:text name="double6.prompt"/></td>
<td><s:text name="double6"/></td>
</tr>
</table>
<br/>
<s:url id="url" action="FormDouble!input"/>
<s:a href="%{url}"><s:text name="Confirmation.lien"/></s:a>
</body>
</html>
视图 [ConfirmationFormDouble.jsp] 仅用于显示模板 [FormDoubleModel]。第 47-49 行展示了实数的显示效果。我们希望该显示能遵循应用程序的 localisation 规范。 根据该规范,实数在法国(10,7)和英国(10.7)的显示方式不同。为此,我们使用此前用于应用程序国际化的 <s:text> 标签。因此,该标签同样适用于本地化。
此处使用的消息键为 double.format。该键位于文件 [FormDoubleModel.properties] 中:
double.format={0,number}
该键对应的值并非消息本身,而是一种显示格式:
- 0 是一个表示待显示值的参数。在此处,它将对应模板中的字段 double5。
- number 代表数字格式。该格式将根据各国情况进行调整。如果没有此格式,无论在哪个国家,数字 10.7 都将始终显示为 10.7。
标签
<s:text name="double.format">
<s:param value="double5"/>
</s:text>
将显示消息 {0, number},其中参数 double5(第 2 行)将替换格式中的参数 0。因此,模板 double5 将以本地化的数字格式显示。
13.6. 模板 [FormDoubleModel]
表单 [FormDouble.jsp] 中的字段 double1 至 double6 将注入到以下模板 [FormDoubleModel] 中:
package example;
public class FormDoubleModel {
// 无参构造函数
public FormDoubleModel() {
}
// 字段
private String double1;
private Double double2;
private Double double3;
private Double double4;
private Double double5;
private String double6;
// 清空模型
public void clearModel() {
double1 = null;
double2 = null;
double3 = null;
double4 = null;
double5 = null;
double6 = null;
}
// 获取器和设置器
...
}
- 字段 double1 和 double6 的类型为 String
- 其余字段的类型为 Double
13.7. 模型的验证
模型的验证由两个文件控制:[FormDouble-validation.xml] 和 [FormDoubleModel-validation.xml]。
文件 [FormDouble-validation.xml] 将验证任务委托给 [FormDoubleModel-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/example-10/example/xwork-validator-1.0.2.dtd">
<validators>
<field name="model" >
<field-validator type="visitor">
<param name="appendPrefix">false</param>
<message/>
</field-validator>
</field>
</validators>
我们已经遇到过这个文件。
文件 [FormDoubleModel-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/example-10/example/xwork-validator-1.0.2.dtd">
<validators>
<field name="double1" >
<field-validator type="requiredstring" short-circuit="true">
<message key="double1.error"/>
</field-validator>
<field-validator type="regex" short-circuit="true">
<param name="expression">^[+|-]*\s*\d+(,\d+)*$</param>
<param name="trim">true</param>
<message key="double1.error"/>
</field-validator>
</field>
<field name="double2" >
<field-validator type="required" short-circuit="true">
<message key="double2.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="double2.error"/>
</field-validator>
</field>
<field name="double3" >
<field-validator type="required" short-circuit="true">
<message key="double3.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="double3.error"/>
</field-validator>
<field-validator type="double" short-circuit="true">
<param name="minInclusive">2.64</param>
<message key="double3.error"/>
</field-validator>
</field>
<field name="double4" >
<field-validator type="required" short-circuit="true">
<message key="double4.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="double4.error"/>
</field-validator>
<field-validator type="double" short-circuit="true">
<param name="maxExclusive">8.32</param>
<message key="double4.error"/>
</field-validator>
</field>
<field name="double5" >
<field-validator type="required" short-circuit="true">
<message key="double5.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="double5.error"/>
</field-validator>
<field-validator type="double" short-circuit="true">
<param name="minInclusive">2.64</param>
<param name="maxExclusive">8.32</param>
<message key="double5.error"/>
</field-validator>
</field>
<field name="double6" >
<field-validator type="requiredstring" short-circuit="true">
<message key="double6.error"/>
</field-validator>
<field-validator type="regex" short-circuit="true">
<param name="expression">^[+|-]*\s*\d+(,\d+)*$</param>
<param name="trim">true</param>
<message key="double6.error"/>
</field-validator>
</field>
</validators>
- 第 12-21 行的规则用于验证输入字段 double1 是否符合表示实数的正则表达式模式。
- 第 23-30 行的规则验证输入字段 double2 能否转换为双精度实数。
- 第 32-43 行的规则验证输入字段 double3 能否转换为 >=2.64 的双精度实数。请注意,必须使用英制实数表示法。
- 第 45-56 行的规则验证输入字段 double4 是否可转换为小于等于 8.32 的双精度实数。
- 第58-70行的规则验证输入字段double5是否可转换为[2.64,8.32[区间内的双精度实数。
- 第 72-80 行的规则检查字段 double6 是否符合表示实数的正则表达式模式。
一旦文件 [FormDoubleModel-validation.xml] 被验证拦截器处理,该拦截器将调用操作 [FormDouble] 中的方法 validate(如果该方法存在)。我们将结合整个操作来介绍该方法。
13.8. 操作 [FormDouble]
操作 [FormDouble] 如下:
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 FormDouble extends ActionSupport implements ModelDriven, SessionAware {
// 无参构造函数
public FormDouble() {
}
// 操作模型
public Object getModel() {
if (session.get("model") == null) {
session.put("model", new FormDoubleModel());
}
return session.get("model");
}
@SkipValidation
public String clearModel() {
// 模型清空
((FormDoubleModel) getModel()).clearModel();
// 结果
return INPUT;
}
public String cancel() {
// 清理模型
((FormDoubleModel) getModel()).clearModel();
// 结果
return "cancel";
}
// SessionAware
Map<String, Object> session;
public void setSession(Map<String, Object> session) {
this.session = session;
}
// 验证
@Override
public void validate() {
// 重复输入6是否有效?
if (getFieldErrors().get("double6") == null) {
// 将字符串 double6 中的逗号替换为句点
String strDouble6 = (((FormDoubleModel) getModel()).getDouble6()).replace(',', '.');
// 字符串 --> 双精度
double double6 = Double.parseDouble(strDouble6);
// 验证
if (double6 < 2.64 || double6 > 8.32) {
addFieldError("double6", getText("double6.error"));
}
}
}
}
操作 [FormDouble] 基于与操作 [FormInt] 相同的模型构建。 我们仅对方法 validate 进行说明。需要提醒的是,方法 validate 在处理验证文件 [FormDoubleModel-validation.xml] 之后、执行方法 execute 之前执行。
- 第48行:如果字段double6上已有错误,则不再进行任何操作。
- 第 50 行:获得了一个形式为 45,67 的字符串。该字符串已存储在模型的 double6 字段中。将逗号替换为小数点,得到 45.67。
- 第 52 行:字符串 45.67 被转换为双精度实数。这应该有效,因为字段 double6 符合实数格式。
- 第 54 行:验证所得的双精度实数是否在 [2.64, 8.32] 区间内。
- 第55行:若不在该区间内,则将错误信息double6.error附加到字段double6中。该信息将保存在文件[FormDoubleModel.properties]中,并在错误表单重新显示时呈现。
13.9. 最后细节
现在,让我们回到表单 [FormDouble.jsp] 输入字段的复杂性上。 我们将以字段 double2 为例进行分析。该分析同样适用于采用 Double 类型模板的字段 double3 至 double5。 对于采用 String 类型模板的字段 double1 和 double6,不存在问题。
输入字段 double2 如下所示:
<s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' :getText('double.format',{double2})}"/>
我们从最简单的标签开始:
<s:textfield name="double2" key="double2.prompt"/>
并观察其效果:
![]() |
- 在 [1] 中,我们验证了正确的数字 double2。在屏幕截图中可能不太明显,但我们输入的数字带有一个逗号。
- 在 [2] 中,这是确认视图。输入的 double2 已通过验证测试。该数字以带小数点的形式显示。
![]() |
- 在 [3] 中,返回表单
- 在 [4] 中,显示表单。屏幕截图未能清晰显示的是,数字 double2 最初为 4,32,现已变为 4.32(带小数点)。
![]() |
- 在 [5] 中,我们不做任何修改就重新提交表单
- 变为 [6],字段 double2 报错。
问题如下:
- 最初在 [1] 中,输入字符串“4,32”已成功转换为实数 4.32。这意味着字符串转双精度浮点数(String --> Double)的操作成功,因此在此情况下,Struts 会考虑区域设置(此处为法国)。
- 新表单 [4] 在字段 double2 中显示实数 4.32 的值。 由于未对显示进行本地化,系统默认采用英式格式 c.a.d 并使用小数点。因此,在 Double 转 String 的过程中,Struts 不再考虑区域设置,否则本应显示带逗号的 4,32。
这至少可以说是不一致的。不过没关系,我们将对数字 4.32 的显示进行本地化。输入标签变为如下所示:
<s:textfield name="double2" key="double2.prompt" value="%{getText('double.format',{double2})}"/>
value 属性指定了要在 double2 字段中显示的值。该值来自表达式 OGNL。 回顾文件 [FormDoubleModel.properties] 中键 double.format 的定义:
double.format={0,number}
方法 getText('键') 可用于获取与某个键关联的消息。该消息会在与当前区域设置对应的文件中进行查找。 因此,如果当前语言环境为 es(西班牙语),则会从文件 [FormDoubleModel_es.properties] 中查找键 double.format。
方法 getText('键', {param0, param1, ...}) 用于检索带参数的消息。消息
是由参数 0 配置的消息。该参数为位置参数。方法 getText('double.format', {double2}) 将把数值 double2 赋值给参数 0。 最终,我们要求以本地化的数字格式获取 double2 的值。在法国,数字 4.56 将被本地化为字符串 "4,56"。
完成此转换后,重新进行测试。
![]() |
表单初始显示时,[1] 便出现了异常。让我们回到该标签:
<s:textfield name="double2" key="double2.prompt" value="%{getText('double.format',{double2})}"/>
在初始显示时,模板 double2 的值为 null,这是一个非数字值。我们将该标签修改如下:
<s:textfield name="double2" key="double2.prompt" value="%{double2==null ? '' : getText('double.format',{double2})}"/>
这次,我们测试是否为 double2==null。如果是,则显示一个空字符串。
完成此更改后,我们继续进行测试:
![]() |
![]() |
从 [1] 到 [4] 的屏幕显示,我们试图解决的问题已得到解决:
- 在 [1] 中,输入 4.67
- 在 [2] 中,该数值已被接受
- 在 [3] 中,该数值再次显示为 4.67(带小数点),这一点在 [4] 中得到了确认。
遗憾的是,问题并未就此结束。让我们看看下一段代码:
![]() |
- 在 [5] 中,添加了一个字符以使 double2 失效
- 在 [6] 中,验证测试发挥了作用,并报告了错误。但 [6] 中显示的字符串并非错误信息,而是当前表单 double2 的值。我们丢失了输入的值。
当从 [5] 切换到 [6] 时,请求未能执行完毕。它被验证拦截器中断。 在 [6] 时,显示的是 double2 的值,由于此次中断,该字段未接收新值。因此显示的是其先前值,而本应显示输入的字符串。 可通过 #parameters['param'] 语法查看请求参数。我们将输入字段 double2 修改如下:
<s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' : getText('double.format',{double2})}"/>
字段 double2 的显示值计算方式如下:如果存在 'double2' 参数,则显示该值;否则显示模板 double2。请读者测试此新版标签是否解决了遇到的问题。
13.10. Conclusion
令人惊讶的是,带有效性检查的实数输入竟然如此复杂……或许是我在文档中遗漏了什么?








