3. 控制器对表单的处理
接下来我们将重点探讨,当用户点击表单上的 [提交] 按钮时,控制器是如何处理表单值的。
3.1. struts-config.xml 文件
Struts 控制器的新 struts-config.xml 配置文件如下所示:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean
name="frmPersonne"
type="istia.st.struts.personne.FormulaireBean"
/>
</form-beans>
<action-mappings>
<action
path="/main"
name="frmPersonne"
scope="session"
validate="true"
input="/erreurs.do"
parameter="/vues/main.html"
type="org.apache.struts.actions.ForwardAction"
/>
<action
path="/erreurs"
parameter="/vues/erreurs.personne.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
<action
path="/reponse"
parameter="/vues/reponse.personne.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
<action
path="/formulaire"
parameter="/vues/formulaire.personne.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
</action-mappings>
<message-resources parameter="ressources.personneressources"/>
</struts-config>
我们已标出更改之处:
- 新增了一个 <form-beans> 部分。它用于定义与应用程序中每个表单关联的类。<form-beans> 标签的数量必须与应用程序中不同的表单数量一致。这里我们只有一个表单,因此只有一个 <form-beans> 部分。对于每个表单,我们必须定义:
- 表单名称(name 属性)
- 负责存储表单值的、从 ActionForm 派生的类名(type 属性)
这两个属性不能随意设定。它们必须与表单 HTML 代码中 <html:form> 标签所使用的名称一致。回顾一下 (name, age) 表单的代码:
必须在 struts-config.html 文件中以相同的方式声明该表单。具体操作如下:
- /main 动作的配置已发生变更。该动作负责处理表单值。因此,我们必须提供其所需的表单相关信息:
<action
path="/main"
name="frmPersonne"
scope="session"
validate="true"
input="/erreurs.do"
parameter="/vues/main.html"
type="org.apache.struts.actions.ForwardAction"
/>
/main servlet 将处理一个表单,该表单必须指定一个名称。name 属性负责处理此操作。该名称必须指向某个 <form-bean> 部分的 name 属性,在本例中即 frmPersonne。
scope="session" 属性表示表单值必须存储在会话中。这并非总是必要的,但在本例中是必要的。事实上,在 /reponse.do 和 /erreurs.do 视图中,我们发现了一些返回表单的链接。在这两种情况下,我们都希望显示包含用户在先前客户端-服务器交互中输入的值的表单。因此,有必要将会话中的表单存储在会话中。
validate 属性用于指定是否应调用 frmPersonne 对象的 validate 方法。该方法用于验证表单数据的有效性。在此,我们指定必须对数据进行验证,这意味着我们需要在 FormulaireBean 类中编写一个 validate 方法。 表单的 validate 方法由 Struts 控制器在调用 /main Servlet 之前调用。它返回一个 ActionErrors 对象,该对象相当于一个错误列表。如果该列表存在且不为空,Struts 控制器将在此处停止,并将由 input 属性指定的视图作为响应发送。 视图将在请求中收到 ActionErrors 列表,并可通过 <html:errors> 标签将其显示出来。在上文中,我们指定了若出现错误,/main Servlet 必须发送 /errors.do 视图。请注意,该视图与以下 URL 相关联:/views/errors.response.jsp:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Personne</title>
</head>
<body>
<h2>Les erreurs suivantes se sont produites</h2>
<html:errors/>
<html:link page="/formulaire.do">
Retour au formulaire
</html:link>
</body>
</html>
该视图正确使用了 <html:errors> 标签,该标签将显示错误列表。在此错误列表中,您看到的不是错误消息,而是存在于 <message-resources> 标签所引用的文件中的消息标识符(注意:资源名称中的“s”是单数形式):
下面的标签表示应用程序使用的消息文件位于 WEB-INF/classes/ressources/personneressources.properties 文件中:

该文件包含什么内容?这是一个对应于 Java Properties 类的属性文件,即一组键值对行:
errors.header=<ul>
errors.footer=</ul>
personne.formulaire.nom.vide=<li>Vous devez indiquer un nom</li>
personne.formulaire.age.incorrect=<li>L'âge [{0}] est incorrect</li>
此消息文件至少具有两个功能:
- 它允许您在无需重新编译应用程序的情况下修改应用程序的消息
- 它支持 Struts 应用程序的国际化。您可以创建多个资源文件,每种语言对应一个。只要遵循特定的命名约定,Struts 就会自动使用正确的消息文件。
- 如果表单的 validate 方法返回一个空的错误列表,那么 Struts 控制器将调用 ForwardAction Servlet 的 execute 方法。这里需要理解的是,当 Servlet 的 execute 方法运行时,意味着表单数据已被视为有效(当然,前提是已通过 validate="true" 进行了验证)。开发人员实际上是在与该 Action 关联的 Servlet 的 execute 方法中处理表单的。 此处是处理的核心所在(应用逻辑、业务类和数据访问类的调用)。最终,该方法会返回一个 ActionForward 对象,用于告知渲染器应将哪个视图发回给客户端。这里我们使用了 Struts 预定义的 ForwardAction 动作。其 execute 方法仅返回一个指向由 parameter 属性指定的 URL 的 ActionForward:
<action
path="/main"
name="frmPersonne"
validate="true"
input="/erreurs.do"
parameter="/vues/main.html"
type="org.apache.struts.actions.ForwardAction"
/>
因此,如果表单数据有效,/main 操作将返回我们之前已经用过的 /vues/main.html 视图。
3.2. 新的 FormBean 类
我们已经创建了 FormBean 类的初始版本,该类负责存储来自 formulaire.personne.jsp 表单的数据(姓名、年龄)。该版本并未对数据进行验证。现在,我们必须进行验证,因为我们在 struts-config.xml 文件中指定了表单数据在传递给 ForwardAction servlet 之前必须经过验证(validate="true")。该类的代码如下:
package istia.st.struts.personne;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class FormulaireBean
extends ActionForm {
// name
private String nom = null;
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
// age
private String age = null;
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
// validation
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
// error management
ActionErrors erreurs = new ActionErrors();
// name must be non-empty
if (nom == null || nom.trim().equals("")) {
erreurs.add("nomvide", new ActionError("personne.formulaire.nom.vide"));
// age must be a positive integer
}
if (age == null || age.trim().equals("")) {
erreurs.add("agevide", new ActionError("personne.formulaire.age.vide"));
}
else {
// age must be a positive integer
if (!age.matches("^\\s*\\d+\\s*$")) {
erreurs.add("ageincorrect", new ActionError("personne.formulaire.age.incorrect", age));
// return the list of errors
}
} //if
// return the error list
return erreurs;
}
}
新功能体现在 validate 方法的实现上。该方法由 Struts 控制器调用,在控制器将表单中同名字段的值填入类中的 name 和 age 属性后,必须验证 name 和 age 属性的有效性。上面的代码非常直观:
- 创建一个空的错误列表 (ActionErrors errors)
- 检查 name 字段。如果为空,则使用 ActionErrors.add("key", ActionError) 方法将错误添加到 errors 列表中。
- 如果 age 字段不是整数,则执行同样的操作。
- validate 方法将错误列表 (ActionErrors errors) 返回给 Struts 控制器。如果 errors 为 null 或 errors.size() 为 0,控制器则认为没有错误。随后,它将执行与该操作(type="org.apache.struts.actions.ForwardAction")关联的 Action 类的 execute 方法。 否则,它将返回与表单错误关联的视图(input="/errors.do")。
通过 ActionErrors.add("errorKey", new ActionError("messageKey"[,param0, param1, param2, param3])) 将一个错误添加到 ActionErrors 列表中。第一个参数 "errorKey" 用于在 ActionErrors 列表中唯一标识一个 ActionError 元素,这与字典中的做法类似。它可以是任意字符串。 ActionError 是一个通过其构造函数 ActionError(String errorKey[,String param0, String param1, String param2, String param3]) 与错误消息关联的对象,其中 errorKey 是与该错误相关联的消息标识符,以及最多 4 个可选参数。keyMessage 标识符并非任意设定,而是 struts-config.xml 文件中 <message-resources> 标签指定的文件内所包含的标识符之一:
请注意,该文件(实际上是 WEB-INF/classes/resources/personneressources.properties)包含以下键:
errors.header=<ul>
errors.footer=</ul>
personne.formulaire.nom.vide=<li>Vous devez indiquer un nom</li>
personne.formulaire.age.incorrect=<li>L'âge [{0}] est incorrect</li>
我们可以验证,FormBean 类的 validate 方法所使用的消息键确实存在于上述文件中。我们为每条错误消息使用了 HTML 标签 <li>,以便 <html:errors> 标签将其显示为 HTML 列表。我们已经看到,ActionError 对象不仅可以使用消息键来构造,还可以使用其他参数:
如果 ActionError 对象是使用附加参数(最多四个)构造的,则可以通过 {0} 到 {3} 的表示法在消息文本中访问这些参数。因此,FormulaireBean 的 validate 方法会构造一个 ActionError 对象,其键为 personne.formulaire.age.incorrect,并带有附加参数 param0 age:
.properties 文件中与键 **person.form.age.incorrect** 关联的消息是
其中 {0} 将被年龄值替换。最后,键为 errors.header 和 errors.footer 的消息将分别写在错误列表的前后。在此,这两个键将用于包含 HTML 标签 <ul> 和 </ul>,这些标签必须包围 <li> 标签。
3.3. 表单验证测试
现在我们可以测试表单的有效性了。以下是应用程序各组件应放置位置的提醒:
![]() | |
![]() | |
![]() | |
![]() |
3.3.1. 测试 1
重启 Tomcat 使其读取新的配置文件,然后访问 URL http://localhost:8080/strutspersonne/formulaire.do:

说明:
- 在 struts-config.html 中,使用了以下部分:
<action
path="/formulaire"
parameter="/vues/formulaire.personne.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
如果查看接收到的页面的 HTML 代码,我们会发现页面上的 <form> 标签如下所示:
因此,类型为 submit 的 [提交] 按钮将把表单数据发送至 URL /strutspersonne/main.do。
3.3.2. 测试 2
让我们在输入字段留空的情况下点击[提交]按钮。我们会得到以下响应:

说明:
- 如上所述,表单数据被发送到了 URL /strutspersonne/main.do。随后使用了 struts-config.xml 文件中的以下部分:
<form-bean
name="frmPersonne"
type="istia.st.struts.personne.FormulaireBean"
scope="session"
/>
....
<action
path="/main"
name="frmPersonne"
validate="true"
input="/erreurs.do"
parameter="/vues/main.html"
type="org.apache.struts.actions.ForwardAction"
/>
已触发 /main 操作。该操作使用了 frmPersonne 表单(name="frmPersonne")。 因此,Struts 控制器已根据需要实例化了一个 FormulaireBean 类的对象(form-bean 标签中的 type="istia.st.struts.personne.FormulaireBean")。它已将该对象的 name 和 age 属性填充为 HTML 表单中同名的字段值:
<table>
<tr>
<td>Nom</td>
<td><html:text property="nom" size="20"/></td>
</tr>
<tr>
<td>Age</td>
<td><html:text property="age" size="3"/></td>
</tr>
<tr>
</table>
完成上述操作后,Struts 控制器会调用 FormBean 对象的 validate 方法,因为在配置文件中,/main 动作的 validate 属性被设置为 true:
<action
path="/main"
name="frmPersonne"
validate="true"
input="/erreurs.do"
parameter="/vues/main.html"
type="org.apache.struts.actions.ForwardAction"
/>
FormBean 类的 validate 方法如下:
// validation
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
// error management
ActionErrors erreurs = new ActionErrors();
// name must be non-empty
if (nom == null || nom.trim().equals("")) {
erreurs.add("nomvide", new ActionError("personne.formulaire.nom.vide"));
// age must be a positive integer
}
if (age == null || age.trim().equals("")) {
erreurs.add("agevide", new ActionError("personne.formulaire.age.vide"));
}
else {
// age must be a positive integer
if (!age.matches("^\\s*\\d+\\s*$")) {
erreurs.add("ageincorrect", new ActionError("personne.formulaire.age.incorrect", age));
// return the list of errors
}
} //if
// return the error list
return erreurs;
}
由于 [name] 和 [age] 字段为空,上面的 validate 方法生成了一个包含两个错误的列表,并将其返回给 Struts 控制器。由于存在错误,控制器随后将与该输入属性关联的视图返回给客户端。为了确定这是哪个视图,它使用了配置文件中的以下部分:
<action
path="/erreurs"
parameter="/vues/erreurs.personne.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
因此,它最终发送了视图 /views/errors.person.jsp。该视图包含以下代码:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Personne</title>
</head>
<body>
<h2>Les erreurs suivantes se sont produites</h2>
<html:errors/>
<html:link page="/formulaire.do">
Retour au formulaire
</html:link>
</body>
</html>
<html:errors> 标签仅用于显示 Struts 控制器发送给它的消息列表。它使用由 <message-resources> 标签指定的消息文件:
该文件包含以下键和消息:
personne.formulaire.nom.vide=<li>Vous devez indiquer un nom</li>
personne.formulaire.age.vide=<li>Vous devez indiquer un age</li>
personne.formulaire.age.incorrect=<li>L'âge [{0}] est incorrect</li>
errors.header=<ul>
errors.footer=</ul>
- 与 errors.header 键关联的消息会被写入
- 写入与接收到的 ActionErrors 列表中各个键关联的消息
- 与 errors.footer 键关联的消息被写入
3.3.3. 测试 3
让我们点击错误页面上的 [返回表单] 链接。我们将看到以下页面:

说明:
- [返回表单]链接的HTML代码如下:
Struts 控制器在其配置文件中使用了以下部分:
<action
path="/formulaire"
parameter="/vues/formulaire.personne.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
因此,它返回了视图 /views/form.person.jsp。
3.3.4. 测试 4
我们填写以下表单,然后点击 [提交] 按钮:

我们将收到以下响应:

说明:与测试 2 相同。
3.3.5. 测试 5
我们点击上方的 [返回表单] 链接。我们看到以下页面:

我们可以看到,表单的状态与提交时完全一致。
说明:这些说明与测试 #3 相同,并补充以下信息:
- 显示的 HTML 表单包含以下标签:
<table>
<tr>
<td>Nom</td>
<td><html:text property="nom" size="20"/></td>
</tr>
<tr>
<td>Age</td>
<td><html:text property="age" size="3"/></td>
</tr>
<tr>
</table>
<html:text> 标签有两个功能:
- 当从客户端向服务器发送表单值时,表单输入字段的值会被赋给 FormBean 对象中同名的字段
- 当服务器将待显示的表单 HTML 代码发回客户端时,与 <html:text> 标签关联的输入字段的 value 属性将初始化为 FormBean 对象中同名字段的值。
这里有两种不同的客户端-服务器交互:
- 在第一种情况下,用户填写了表单并将其提交给服务器
- 在第二种情况下,用户点击了 [返回表单] 链接以返回表单。
在第二种交互中,表单要重新显示其原始值,唯一的方法是将这些值存储在客户端的会话中。这就是在配置 /main 操作的章节中指定的内容:
<action
path="/main"
name="frmPersonne"
scope="session"
validate="true"
input="/erreurs.do"
parameter="/vues/main.html"
type="org.apache.struts.actions.ForwardAction"
/>
如果我们设置了 scope="request",表单数据就不会存储在会话中,我们也就无法在第二次交互中检索其值。
3.3.6. 测试 6
让我们回到表单,这次输入有效数据:

提交表单。我们得到以下结果:

说明:
- 由于 [提交] 按钮将表单值发送至 URL /strutspersonne/main.do,因此在 Struts 控制器接收到 FormBean 的 validate 方法返回的 ActionErrors 结果之前,其原理与测试 2 相同。但在此处,该列表为空。随后,控制器会使用 /main 动作配置中的新部分:
<action
path="/main"
name="frmPersonne"
scope="session"
validate="true"
input="/erreurs.do"
parameter="/vues/main.html"
type="org.apache.struts.actions.ForwardAction"
/>
Struts 控制器会在必要时创建一个由 type 属性指定的类型的对象。该类的 execute 方法会被执行,并必须返回一个 ActionForward 对象,该对象指明了控制器必须作为响应发送给客户端的视图。在此,type 属性指向预定义的 ForwardAction 类。 该类的 execute 方法不执行任何操作,仅返回一个指向由 parameter 属性定义的视图的 ActionForward 对象,在本例中即 /vues/main.html 视图。这确实是控制器返回的视图。
3.3.7. 测试 7
我们再次请求 /form.do 视图:

我们看到表单与提交时完全一致。原因已在前文说明。通过配置(scope="session"),我们指定表单应保存在会话中。因此,其值在客户端与服务器的交互过程中得以保留。
我们已经快完成了。我们还需要为表单数据有效时创建一个合适的操作。目前,我们使用了预定义的 ForwardAction 来简化演示。
3.4. /main 操作的新配置
我们不会修改当前的 struts-config.xml 配置文件,仅对其 /main 部分进行如下修改:
<action
path="/main"
name="frmPersonne"
scope="session"
validate="true"
input="/erreurs.do"
type="istia.st.struts.personne.FormulaireAction"
>
<forward name="reponse" path="/reponse.do"/>
</action>
type 属性现在指向另一个名为 FormAction 的类,我们需要实现该类。如果 frmPersonne 表单中的数据有效,将调用该类的 execute 方法。 我们已指定 **execute** 方法在完成任务后,返回一个 ActionForward 对象,该对象指定了控制器应返回给客户端的视图。根据表单处理的结果,通常会有多个可能的视图。这些可能的视图列表在 <**action**> 标签内的 <**forward**> 标签中指定。此类标签的语法如下:
用于唯一标识视图的任意名称 | |
与该键关联的视图的 URL |
3.5. FormAction 类
编写 FormAction 类本质上就是编写其 execute 方法:
package istia.st.struts.personne;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.servlet.ServletException;
public class FormulaireAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws IOException,ServletException {
// we have a valid form, otherwise we wouldn't have got here
FormulaireBean formulaire=(FormulaireBean)form;
request.setAttribute("nom",formulaire.getNom());
request.setAttribute("age",formulaire.getAge());
return mapping.findForward("reponse");
}//execute
}
execute 方法接受四个参数:
- ActionMapping mapping:一个表示当前正在执行的操作配置的“映像”对象;在此情况下,该映像具有以下配置:
<action
path="/main"
name="frmPersonne"
validate="true"
input="/erreurs.do"
type="istia.st.struts.personne.FormulaireAction"
>
<forward name="reponse" path="/reponse.do"/>
</action>
因此,该操作可以访问与视图关联的键,这些键可在操作结束时返回给客户端。execute 方法必须返回其中一个键。
- ActionForm form:包含当前操作所用表单值的 Bean 对象。此处为类型为 FormBean 的 frmPersonne 对象。因此,该操作可以访问表单值。
- HttpServletRequest request:客户端请求,可能已被各种 Servlet 进行了扩展。因此,Action 可以访问初始请求的所有参数(request.getParameter),以及添加到该初始请求的所有属性(request.getAttribute)。 在本例中,execute 方法通过添加姓名和年龄来丰富请求。这在此处完全没有必要,因为这两个值已经存在,只是作为参数而非属性。此处包含该代码仅用于说明。
- HttpServletResponse response:将发送给客户端的响应。Action 方法可以对该响应进行扩展,但在此处并未进行。
这里我们处理的是一个特例。execute 方法几乎无需执行任何操作。它只需指出下一个视图是 /response.do,并在请求中指定该视图将接收其显示所需的 name 和 age 信息。它通过 ActionMapping 类的 findForward 方法实现这一点,该方法的参数是动作配置中 forward 标签中找到的某个键。这里只有一个这样的标签:
因此,我们的方法返回一个以“response”为键的 ActionForward,以指示应发送 /response.do 视图。
3.6. FormAction 测试
我们使用 JBuilder 编译前面的类,并将生成的 .class 文件放置在 WEB-INF/classes 目录下:

我们修改视图 /vues/reponse.personne.jsp:
<%
// on récupère les données nom, age
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
%>
<html>
<head>
<title>Personne</title>
</head>
<body>
<h2>Personne - réponse</h2>
<hr>
<table>
<tr>
<td>Nom</td>
<td><%= nom %>
</tr>
<tr>
<td>Age</td>
<td><%= age %>
</tr>
</table>
<html:link page="/formulaire.do">
Retour au formulaire
</html:link>
</body>
</html>
该视图从接收到的请求属性中获取姓名和年龄信息。我们通过 URL http://localhost:8080/strutspersonne/formulaire.do 请求表单,然后填写表单:

我们点击 [提交] 按钮,并收到以下响应:

说明:
- 关于流程的开始部分,我们将参考测试 #2 中的说明。让我们回顾一下 /main 操作的配置:
<action
path="/main"
name="frmPersonne"
scope="session"
validate="true"
input="/erreurs.do"
type="istia.st.struts.personne.FormulaireAction"
>
<forward name="reponse" path="/reponse.do"/>
</action>
- 表单提交至 URL /main.do 处的控制器后,控制器创建或复用了类型为 FormBean 的 frmPersonne 对象,并将其填充为表单值
- 调用了 frmPersonne 对象的 validate 方法。由于数据有效,validate 方法返回了一个空的 ActionErrors 列表。
- 创建或回收了一个 FormAction 对象,并调用了其 execute 方法。该方法返回了一个键为 "reponse" 的 ActionForward 对象。
- 随后,控制器发送了与“reponse”键关联的视图,即 /reponse.do,也就是 /vues/reponse.personne.jsp。
- 视图 reponse.personne.jsp 被显示出来,其中包含由 FormAction 对象的 execute 方法在请求中设置的值。
3.7. 结论
我们构建了一个功能全面却又简单的应用程序。在实际使用 Struts、Tomcat 和 JBuilder 进行开发时,很容易出现错误,尤其是在应用程序的 XML 配置文件中。乍一看,不使用 Struts,而是采用 Servlet 和 JSP 页面来构建这个应用程序似乎更简单。对于初学者来说,这可能是真的。但随着经验的积累,使用 Struts 进行开发会变得更加容易。 许多公司出于以下原因,在Web开发中强制采用Struts开发方法:
- Struts 遵循 MVC 模型
- 当所有开发人员采用统一的工作方式时,由于具备标准化的架构,应用程序的维护工作将变得更加轻松。



