7. QuiEst 应用程序
在此,我们将介绍一个比之前那些应用程序稍显复杂的 Struts 应用程序。此前那些应用程序为了教学目的而设计得较为简单。
7.1. Users 类
我们有一个 Java 类,用于存储 Unix 机器上用户的相关信息。这些信息存储在三个特定文件中:
- /etc/passwd:用户列表
- /etc/group:用户组列表
- /etc/aliases:电子邮件别名列表
这三个文件的内容如下:
- /etc/passwd
该文件中的行采用以下格式:
login:pwd:uid:gid:id:dir:shell
其中
用户登录 | |
其加密密码 | |
用户 ID | |
他的组ID | |
他的身份 | |
他的登录目录 | |
他的 shell |
因此,用户的命令行可能如下所示:
上述用户的 ID 为 110,属于组 57。组 57 的定义可在 /etc/group 文件中找到。
- /etc/group
该文件中的行采用以下格式:
nomGroupe:pwd:gid:membre1,membre2,....
其中
组名 | |
加密密码 - 此字段通常为空 | |
组 ID | |
用户登录名 - 此字段可以为空 |
因此,上述第 57 组的配置行可以如下所示:
这表示第57组被命名为iup2-auto。
- /etc/aliases
该文件中的行采用以下格式:
使用
别名 | |
一个或多个制表符 | |
该别名所属用户的登录名 |
因此,该行
guillaume.dupond: dupond
表示别名 guillaume.dupond 属于登录名为 dupond 的用户。 请记住,别名用于电子邮件地址中。因此,如果在上例中,Unix 机器名为 shiva.istia.univ-angers.fr,那么发往 guillaume.dupond@shiva.istia.univ-angers.fr 的电子邮件将被投递到该机器上登录名为 dupond 的用户的邮箱中。
这里我们不会详细探讨 users 类的整个接口,而是仅关注其构造函数和几个方法:
import java.io.*;
import java.util.*;
public class users{
// attributes
private Hashtable usersByLogin=new Hashtable(); // login --> login, pwd, ..., dir
private ArrayList erreurs=new ArrayList(); // list of error messages
....
// manufacturer
public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
// usersFileName: name of the user file with lines of the form
// login:pwd:uid:gid:id:dir:shell
// groupsFileName: name of the group file with lines of the form
// name:pwd:number:member1,member2,...
// aliasesFileName: name of alias file with lines of the form
// alias:[tab]login
// builds the usersByLogin dictionary
....
}// manufacturer
// user list
public Hashtable getUsersByLogin(){
return usersByLogin;
}
// errors
public ArrayList getErreurs(){
return erreurs;
}
一个字典(哈希表),其键为 passwd 文件中的登录名。每个键对应的值是一个字符串数组(String[7]),其元素为与该登录名关联的 passwd 文件行中的 7 个字段。如果该行少于 7 个字段,则某些字段可能为空。 | |
错误消息列表——若无错误则为空 |
7.2. 该网络应用程序
我们建议构建以下 Web 应用程序(表单页面):
![]() |
编号 | 名称 | HTML 类型 | 角色 |
1 | cmbLogins | <select ...>...</select> | 显示所有可请求信息的登录项列表 |
2 | btnSearch | <input type="submit" ...> | 用于启动搜索 |
当用户点击 [搜索] 按钮 (2) 时,系统会从类型为 users 的 U 对象中查询登录信息 (1)。如果该登录信息存在,则返回以下响应(信息页面):
![]() |
如上方的浏览器 URL 所示,表单参数是通过 GET 请求发送至服务器的。因此,我们可以直接向浏览器提供包含这些参数的 URL。这就是我们在此处所做的操作,即输入一个不存在的登录名。我们将获得以下响应(错误页面):
![]() |
7.3. 应用程序架构
![]() |
该架构包括以下组件:
- 视图:
- logins.jsp,用于显示登录列表(视图 1)
- infos.jsp,用于显示与登录相关的信息(视图 2)
- erreurs.jsp,用于显示错误列表(视图 3)
- 动作使用的 ActionForm 类型表单:
- formLogins,用于收集来自 logins.jsp 表单的数据
- 动作:
- SetupLoginAction,负责准备 form.jsp 的内容并显示该视图
- InfosLoginAction,在 logins.jsp 的内容发送至服务器后对其进行处理
- ForwardAction,负责处理 infos.jsp 和 erreurs.jsp 视图中的 [返回表单] 链接
- 用户业务类,由上述操作用于检索用户数据
- 由三个平面文件 passwd、group 和 aliases 提供的模型
7.4. Web 应用程序配置文件
7.4.1. server.xml 文件
应用程序上下文将命名为 /strutsquiest2。因此,我们将向 Tomcat 的 server.xml 文件中添加以下行:
完成此操作后,可能需要重启 Tomcat 以便其识别新上下文。可以通过访问 URL http://localhost:8080/strutsquiest2 来验证其有效性。
7.4.2. web.xml 文件
应用程序的 web.xml 配置文件如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<servlet>
<servlet-name>strutsquiest2</servlet-name>
<servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>passwdFileName</param-name>
<param-value>data/passwd</param-value>
</init-param>
<init-param>
<param-name>groupFileName</param-name>
<param-value>data/group</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>strutsquiest2</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
此 web.xml 文件引入了一项新功能。 Struts 控制器不再是 org.apache.struts.action.ActionServlet,而是一个派生类,我们在此将其命名为 istia.st.struts.quiest.Quiest2ActionServlet。这将允许我们获取两个初始化参数:passwdFileName(密码文件的位置)和 groupFileName(组文件的位置)。此应用程序中不需要别名文件。
7.4.3. struts-config.xml 文件
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="formLogins" type="org.apache.struts.action.DynaActionForm">
<form-property name="cmbLogins" type="java.lang.String" initial=""/>
<form-property name="tLogins" type="java.lang.String[]"/>
</form-bean>
</form-beans>
<action-mappings>
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
<action
path="/retourLogins"
parameter="/vues/logins.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
</action-mappings>
<message-resources
parameter="istia.st.struts.quiest.ApplicationResources"
null="false"
/>
</struts-config>
它包含三个主要部分:
- <form-beans> 部分中的表单声明
- <action-mappings> 部分中的操作声明
- <message-resources> 部分中的资源文件声明
7.4.4. 应用程序的表单对象(Beans)
<form-beans>
<form-bean name="formLogins" type="org.apache.struts.action.DynaActionForm">
<form-property name="cmbLogins" type="java.lang.String" initial=""/>
<form-property name="tLogins" type="java.lang.String[]"/>
</form-bean>
</form-beans>
我们的应用程序中只有一个表单 Bean,名为 formLogins,其类型继承自 DynaActionForm。它将在以下情况下使用:
- 用于存储显示视图 #1 所需的数据
- 在用户提交表单时,从视图 #1 中检索表单的值
formLogins Bean 的结构与视图 #1 中的表单相关联。让我们来看看:
![]() |
编号 | 名称 | HTML 类型 | 角色 |
1 | cmbLogins | <select ...>...</select> | 显示所有可请求信息的登录项列表 |
2 | btnSearch | <input type="submit" ...> | 用于启动搜索 |
让我们区分几种情况:
- 从客户端到服务器,formLogins 对象用于存储上方 HTML 表单中的值,这些值将通过 [Submit] 按钮提交。因此,它需要一个 cmbLogins 字段来接收 HTML cmbLogins 字段的值,即用户选择的登录名。
- 从服务器到客户端,formLogins 对象用于为视图 #1 提供初始内容。其 tLogins 字段将作为列表 #1 的内容。其 cmbLogins 字段将用于设置列表 #1 中待选中的项目。
7.4.5. 应用程序操作
操作由 Action 类型或其派生类型的对象处理。操作在 <action-mappings> 标签内进行配置:
<action-mappings>
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
<action
path="/retourLogins"
parameter="/vues/logins.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
</action-mappings>
/init 操作
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
下面描述一下 /init 操作的工作原理:
- /init 操作通常仅在首次请求-响应循环中发生一次,即当用户请求 URL http://localhost:8080/strutsquiest2/init.do 时
- 此时会创建或回收 formsLogins 对象。该对象会根据 scope 属性的指定,被从会话中检索(回收)或存入会话(创建)。
- 其 reset 方法会被调用。请注意,在 ActionForm 类及其派生类中,该方法默认不执行任何操作。它在将客户端请求的数据复制到 ActionForm 对象之前被调用,用于在复制前清空该对象。这里的客户端请求是什么? 当请求的 URL 为 http://localhost:8080/strutsquiest2/init.do 时,将触发 /init 操作。该 URL 可通过 GET 或 POST 方式进行请求。只需在请求中包含与 formLogins 字段名称对应的参数,即可初始化这些字段,如下例所示:

- 该请求包含 cmbLogins 参数(afterpak)。因此,Struts 控制器将该参数的值复制到了 formLogins 的 cmbLogins 字段中。 随后 SetupLoginsAction 动作被执行,并最终显示了 logins.jsp 视图。该视图包含一个表单,其中某些字段的值来自 formLogins。因此,名为 cmbLogins 的 HTML 下拉选择框从 formLogins 中的 cmbLogins 字段(=afterpak)获取了其值。这就是为什么登录列表显示的位置与 afterpak 登录对应。
- 我们还可以尝试按以下方式传递 tLogins 参数:
这将使用数组 {"login1","login2"} 初始化 formLogins 的 tLogins 字段。然而,正如我们稍后将看到的,SetupLoginsAction 操作会为 tLogins 字段赋值,并将由此创建的数组替换为一个新数组。最终出现在 logins.jsp 视图中的正是这个新数组。
- 前面的讨论虽然有些复杂,但说明我们不能假设 /init 操作会在没有客户端参数的情况下被触发。因此,使用 reset 方法来清空 formLogins 可能会很有用。在这种情况下,我们需要扩展 DynaActionForm 类。我们在此处并未这样做。
- 一旦调用 formLogins 的 reset 方法,控制器会将客户端请求中的数据复制到 formLogins 中同名的字段中。通常,/init 操作是在没有客户端参数的情况下调用的,但我们之前已经说明,没有任何东西能阻止客户端使用任意参数调用 /init 操作。因此,在本阶段结束时,cmbLogins 和 tLogins 字段很可能已经有了值。 我们已经看到,cmbLogins 字段会保留该值,但 tLogins 字段不会。
- 随后,控制器会检查该操作的 validate 属性。此处的值为“false”。formLogins 的 validate 方法将不会被调用。因此,我们无需编写该方法。
- 如果 SetupLoginsAction 对象已存在,则将其回收;否则创建新对象,并调用其 execute 方法。该方法的唯一目的是为 formLogins 的 tLogins 字段赋值。该值是登录名数组,将从 users 业务类中获取。此操作可能会失败。这就是为什么 /init 操作之后可能跟两个视图:
- 如果 users 类无法提供登录名数组,则显示 errors.jsp 视图
- 否则显示 logins.jsp 视图
- 控制器将显示上述两个视图中的一个
- /init 操作的请求-响应循环即告完成。
/infosLogin 操作
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
下面我们来描述一下 /infosLogin 操作的工作原理:
- 当用户点击 logins.jsp 视图上的 [搜索] 按钮时,通常会触发 /infosLogin 操作。随后,系统会根据视图中 <form> HTML 标签的指定内容向服务器发送请求:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
- 我们可以看到,该请求是通过 GET 方法发送至服务器的。因此,用户可以手动输入该地址:

- formsLogins 对象会被创建或回收。它会根据 scope 属性的指定,从会话中检索(回收)或存入会话(创建)。
- 在将客户端请求的数据复制到 ActionForm 对象之前,会调用其 reset 方法。该方法通常采用以下形式:http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=xx,其中 xx 是从登录名列表中选定的登录名。但如果用户通过传递任意参数使用了之前的 URL,该参数也可以是任意值。请考虑以下页面序列:

- 调用 /infosLogin 操作时,传入了参数字符串 cmbLogins=xx&tLogins=login1&tLogins=login2。因此,formLogins 中的 cmbLogins 和 tLogins 字段将分别接收值 "xx" 和 {"login1","login2"}。 /infosLogin 操作将向 users 业务类请求与“xx”登录名关联的信息。users 类会响应称该登录名不存在。因此显示了上方的视图。现在,让我们点击上方的 [返回表单] 链接:

- [返回表单]链接触发了 /retourLogins 操作。该操作仅直接显示 logins.jsp 视图,不执行任何中间操作。请注意,tLogins 字段用于填充 logins.jsp 视图中的登录名列表。由于用户已将该值修改为 {"login1","login2"},因此这两个登录名现在会出现在列表中。 我们再次强调,在应用程序的运行中,必须充分考虑用户或程序设置的任意参数,这一点至关重要。解决此处问题的方案是让 [返回表单] 链接指向 /init 操作。这样可以确保返回正确的登录名列表。
- 让我们回到对 /infosLogin 操作的常规请求,例如:
http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=afterpak
- Struts 控制器会为 ActionForm 对象的 cmbLogins 字段赋值。然而,tLogins 字段将不会被赋值(因为发送的请求中没有对应的字段)。这种行为对我们来说是可行的。因此,我们无需为 formLogins 编写自定义的重置方法。
- 一旦调用 formLogins 的重置方法,控制器会将客户端请求中的数据复制到 formLogins 中同名的字段中。cmbLogins 字段将接收一个值:用户选择的登录名(afterpak)。
- 随后,控制器会检查该操作的 validate 属性。在此处,其值为“false”。因此不会调用 formLogins 的 validate 方法。
- 如果 InfosLoginAction 对象已存在,则将其复用;否则创建该对象,并调用其 execute 方法。该方法的作用是检索与 cmbLogins 登录名关联的信息。这些信息将从用户的业务类中获取。此操作可能会失败(例如,登录名不存在)。这就是为什么 /infosLogin 操作之后可以跟两个视图:
- 如果 users 类无法提供所需的信息,则显示 errors.jsp 视图
- 否则显示 infos.jsp 视图
- 控制器将显示这两个视图中的一个
- /infosLogin 操作的请求-响应循环已完成。
/retourLogins 操作
<action
path="/retourLogins"
parameter="/vues/logins.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
- 点击 erreurs.jsp 和 infos.jsp 页面上的 [返回表单] 链接会触发 /retourLogins 操作。
- 此处该操作未关联任何表单。因此,我们将直接调用 ForwardAction 对象的 execute 方法,该方法将返回一个指向 /vues/logins.jsp 视图的 ActionForward 对象。
7.4.6. 应用程序的消息文件
struts-config.xml 文件的第三部分是消息文件:
ApplicationResources.properties 文件位于 WEB-INF/classes/istia/st/struts/quiest 目录下。其内容如下:
errors.header=<ul>
errors.footer=</ul>
parametreManquant=<li>Le paramètre [{0}] n'a pas été initialisé</li>
usersException=<li>Erreur d'initialisation de l'application : {0}</li>
loginInconnu=<li>Le login [{0}] n'existe pas</li>
7.5. 查看代码
若读者无法理解下文展示的视图代码,建议先复习表单处理相关课程。
7.5.1. logins.jsp 视图
请记住,该视图会在以下两种情况下显示:
- 在首次请求-响应循环中调用 /init 操作时
- 在后续请求-响应循环中调用 /retourLogins 操作时
logins.jsp 视图的代码如下:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Quiest - formulaire</title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<center>
<h2>Application QuiEst</h2>
<hr>
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
<table>
<tr>
<td>Login cherché</td>
<td>
<html:select name="formLogins" property="cmbLogins">
<html:options name="formLogins" property="tLogins"/>
</html:select>
</td>
<td>
<html:submit value="Chercher"/>
</td>
</tr>
</table>
</html:form>
</center>
</body>
</html>
7.5.2. infos.jsp 视图
当成功调用 /infosLogin 操作时,将显示此视图。其代码如下:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<head>
<title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
<hr>
<table border="1">
<tr>
<th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
</tr>
<tr>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>
</tr>
</table>
<br>
<html:link page="/retourLogins.do">
Retour au formulaire
</html:link>
</body>
</html>
此视图使用了一个名为 infosLoginBean 的对象,该对象由 /infosLogin 操作放入请求中。该对象有两个字段:
String titre; // titre à afficher dans la vue
String[] infosLogin; // tableau des informations à afficher dans la vue
在讲解 InfosLoginAction 类的代码时,我们将更详细地讨论这个类。
7.5.3. errors.jsp 视图
当 /init 或 /infosLogin 操作发生错误时,将显示此视图。其代码如下:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Application QuiEst - erreurs</title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<h2 align="center">Application QuiEst - Erreurs</h2>
<hr>
<h2>Les erreurs suivantes se sont produites</h2>
<html:errors/>
<html:link page="/retourLogins.do">
Retour au formulaire
</html:link>
</body>
</html>
7.6. Java 类
web.xml 文件引用了一个 Java 类:
<web-app>
<servlet>
<servlet-name>strutsquiest2</servlet-name>
<servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
....
</servlet>
...
</web-app>
struts-config.xml 配置文件引用了两个 Java 类:
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
7.6.1. Quiest2ActionServlet 类
Quest2ActionServlet 类继承自 ActionServlet 类,即 Struts 的控制器类。我们继承 ActionServlet 类是为了自定义其 init 方法。该方法仅在 Servlet 初始加载时执行一次,允许我们构建一个 users 类型的业务对象。该对象只需构建一次,而 init 方法是执行此构建操作的理想位置。 构建 users 对象需要两个文件:passwd 和 group 文件。这两个文件的位置作为参数在应用程序的 web.xml 文件中传递给 Servlet:
<servlet>
<servlet-name>strutsquiest2</servlet-name>
<servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>passwdFileName</param-name>
<param-value>data/passwd</param-value>
</init-param>
<init-param>
<param-name>groupFileName</param-name>
<param-value>data/group</param-value>
</init-param>
</servlet>
Servlet 代码如下:
package istia.st.struts.quiest;
import java.util.*;
import javax.servlet.*;
import org.apache.struts.action.*;
import istia.st.users.*;
public class Quiest2ActionServlet
extends ActionServlet {
// servlet attributes
private users u = null;
private ActionErrors erreurs = new ActionErrors();
private String[] tLogins;
//init
public void init() throws ServletException {
// don't forget to initialize the parent class
super.init();
// local variables
final String[] initParams = {"passwdFileName", "groupFileName"};
Properties params = new Properties();
// retrieve servlet initialization parameters
ServletConfig config = getServletConfig();
String servletPath = config.getServletContext().getRealPath("/");
for (int i = 0; i < initParams.length; i++) {
String valeur = config.getInitParameter(initParams[i]);
if (valeur == null) {
erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("parametreManquant", initParams[i]));
valeur = "";
}
// the parameter
params.setProperty(initParams[i], valeur);
} //for
// return if there were initialization errors
if (erreurs.size() != 0) {
return;
}
// create a users object
try {
u = new users(servletPath + "/" + params.getProperty("passwdFileName"),
servletPath + "/" + params.getProperty("groupFileName"), null);
}
catch (Exception ex) {
erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("usersException", ex.getMessage()));
return;
} //catch
// retrieve the list of logins
tLogins = new String[u.getUsersByLogin().size()];
Enumeration eLogins = u.getUsersByLogin().keys();
for (int i = 0; i < tLogins.length; i++) {
tLogins[i] = (String) eLogins.nextElement();
}
// sort logins
Arrays.sort(tLogins);
} //init
// servlet private info access method
public Object[] getInfos() {
return new Object[] {erreurs, u, tLogins};
}
}
简而言之,init 方法的工作原理如下:
- 首先,调用父类(ActionServlet)的 init 方法,以便其正确初始化
- 然后读取初始化参数。如果缺少任何参数,则填充私有 ActionErrors 对象的 errors 属性。
- 如果初始化参数存在,则构建一个 users 对象。此构建过程可能会抛出异常。在这种情况下,ActionErrors 的 errors 属性会被填充。
- 如果构造成功,则从创建的对象中检索所有登录记录,将其排序并存入数组,该数组存储在私有属性 `String[] tLogins` 中。
- 构造好的 users 对象将存储在私有属性 users u 中。
- 公共方法 getInfos 将三个私有属性(u、errors、tLogins)检索到一个对象数组中。
7.6.2. SetupLoginsAction 类
此操作的目的是初始化 DynaActionForm 对象 formLogins。该对象被放置在会话中,此后不再需要重新初始化。因此,SetupLoginsAction 仅运行一次。其代码如下:
package istia.st.struts.quiest;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class SetupLoginsAction
extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {
// prepares the form to be displayed
// retrieve info from the controller servlet
// infos=(ActionErrors errors, users u, String[] tLogins)
Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();
// were there any initialization errors?
ActionErrors erreurs = (ActionErrors) infos[0];
if (!erreurs.isEmpty()) {
this.saveErrors(request, erreurs);
return mapping.findForward("afficherErreurs");
}
// put the logins in the form
DynaActionForm formLogins=(DynaActionForm) form;
formLogins.set("tLogins",infos[2]);
return mapping.findForward("afficherLogins");
}
}
与所有 Struts 动作一样,代码位于 execute 方法中。该方法:
- 从 Struts 控制器中检索该控制器通过其 init 方法存储的信息。此操作由 Action 类的 getServlet() 方法完成。
- 这些信息中包含控制器的 ActionErrors 属性。如果该错误列表不为空,则将其放入请求中,并显示 errors.jsp 视图。
- 如果错误列表为空,则将控制器最初创建的登录列表赋值给 formLogins Bean 的 tLogins 字段。随后请求 logins.jsp 视图,该视图将显示登录列表。
7.6.3. InfosLoginBean 和 InfosLoginAction 类
InfosLoginAction 操作的目的是检索与用户所选登录名相关联的信息,并将其呈现给用户。这些信息将收集在一个类型为 InfosLoginBean 的对象中:
package istia.st.struts.quiest;
public class InfosLoginBean implements java.io.Serializable{
// bean holding the info needed for the info page
private String titre;
private String[] infosLogin;
// manufacturer
public InfosLoginBean(String titre, String[] infosLogin){
this.titre=titre;
this.infosLogin=infosLogin;
}
// getters
public String getTitre(){
return this.titre;
}
public String[] getInfosLogin(){
return this.infosLogin;
}
public String getInfosLogin(int i){
return this.infosLogin[i];
}
}
前面的类是一个 Bean,即一个 Java 类,其中私有属性 T unAttribut 会自动关联两个私有方法:
- void setUnAttribut(T value) { unAttribut = value; }
- T getUnAttribut(){ return unAttribut;}
请注意 get 和 set 方法的特殊语法。如果属性是一个数组 T[] unAttribut,我们可以为数组的元素创建 get 和 set 方法:
- void setUnAttribut(T value, int i) { unAttribut[i] = value; }
- T getUnAttribut(int i) { return unAttribut[i]; }
为了更好地理解这一点,让我们看看 infos.jsp 视图的代码,该视图必须在 InfosLoginAction 操作之后发送:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<head>
<title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
<hr>
<table border="1">
<tr>
<th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
</tr>
<tr>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>
</tr>
</table>
<br>
<html:link page="/retourLogins.do">
Retour au formulaire
</html:link>
</body>
</html>
让我们来看以下标签:
它指示系统写入请求(scope)中包含的 infosLoginBean 对象(name)的 title 字段(property)的值。待写入的值将通过 request.getAttribute("infosLoginBean").getTitre() 获取。因此,InfosLoginBean 类中必须存在 getTitre 方法。确实如此。该标签
该语句指示系统写入请求中 infosLoginBean 对象的 infosLogin[0] 元素的值。待写入的值将通过 request.getAttribute("infosLoginBean").getInfosLogin(0) 获取。因此,InfosLoginBean 类中必须存在 getInfosLogin(int i) 方法。该类确实包含此方法。
InfosLoginAction 类的目的是根据用户选择的登录信息构建上述 InfosLoginBean 对象。其代码如下:
package istia.st.struts.quiest;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import istia.st.users.*;
public class InfosLoginAction
extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {
// should display login information
// retrieve info from the controller servlet
// infos=(ActionErrors errors, users u, LoginBean[] tLogins)
Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();
// were there any initialization errors?
ActionErrors erreurs = (ActionErrors) infos[0];
if (!erreurs.isEmpty()) {
this.saveErrors(request, erreurs);
return mapping.findForward("afficherErreurs");
}
// first retrieve this login
String login = (String) ( (DynaActionForm) form).get("cmbLogins");
// do we have anything?
if (login == null) {
// not normal - the login form is returned
DynaActionForm formLogins=(DynaActionForm) form;
formLogins.set("tLogins",infos[2]);
return mapping.findForward("afficherLogins");
}
// we have a login - we're looking for it
String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);
// have we found?
if (infosLogin == null) {
// login not found - error page displayed
ActionErrors erreurs2=new ActionErrors();
erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
this.saveErrors(request, erreurs2);
return mapping.findForward("afficherErreurs");
}
// the login has been found - we put the information found in the query
String titre="Application QuiEst - login["+login+"]";
InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
request.setAttribute("infosLoginBean",infosLoginBean);
return mapping.findForward("afficherInfos");
}
}
execute 方法的工作原理如下:
- 它会检索 Struts 控制器在初始化过程中收集的信息。如果控制器检测到任何错误,执行将在此处停止,并提示用户查看这些错误。
- 我们验证是否存在登录用户。如果用户通过登录选择表单提交,则存在登录用户。然而,用户也可能直接在浏览器中输入该操作的 URL 而未传递任何参数。如果不存在登录用户,我们将重新显示登录用户列表。
- 如果存在登录信息,我们将检索与用户业务类相关联的信息。如果该类无法找到正在搜索的登录信息,则显示错误页面。否则,将创建一个 InfosLoginBean 对象来存储 infos.jsp 视图所需的信息。该对象被放入请求中,随后显示 infos.jsp 页面。
7.7. 部署
应用程序的目录结构如下:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
7.8. 结论
我们在一个实际应用中使用了 Struts 并运用了业务类。我们还演示了必须特别关注客户端发送的请求,且不应对其性质做任何假设。请求可以是任何类型,每个应用程序都必须首先验证其有效性。











