Skip to content

7. QuiEst 应用程序

本文将介绍一个比之前那些应用更复杂的Struts应用程序——之前的应用为了便于教学,都设计得比较简单。

7.1. users类

我们有一个Java类,用于存储Unix系统中用户的相关信息。这些信息被记录在三个特定文件中:

  • /etc/passwd:用户列表
  • /etc/group:用户组列表
  • /etc/aliases:邮件别名列表

这三个文件的内容如下:

- /etc/passwd

该文件中的行格式如下:


    login:pwd:uid:gid:id:dir:shell

其中

login
用户登录名
pwd
其加密密码
uid
用户编号
gid
其组号
id
其身份
dir
其连接目录
shell
其命令解释器

因此,用户的配置行可能如下所示:

dupond:xg675SDFEkl09:110:57:Guillaume Dupond:/home/iup2-auto/dupond:/bin/bash

前一个用户的编号为 110,属于组 57。组 57 的定义可在文件 /etc/group 中找到。

- /etc/group

该文件中的行格式如下:


    nomGroupe:pwd:gid:membre1,membre2,....

其中

nomGroupe
组名
pwd
其加密密码——通常此字段为空
gid
组编号
membrei
用户登录名——该字段可能为空

因此,前述第57组的行可能如下所示:

iup2-auto::57:

这表明第 57 组名为 iup2-auto

- /etc/aliases

该文件中的行格式如下:

alias:[tab]login

其中

alias
别名
[tab]
一个或多个制表符
login
别名所属用户的登录名

因此,该行


    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{


   // 属性
  private Hashtable usersByLogin=new Hashtable();       // 登录 --> 用户名、密码、...、目录
    private ArrayList erreurs=new ArrayList();             // 错误消息列表

....

   // 构造函数
  public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
        // usersFileName:用户文件名,其中包含以下格式的行
         // 登录名:密码:用户ID:组ID:ID:目录:shell
         // groupsFileName:包含以下格式行组的文件名
         // 名称:密码:编号:成员1,成员2,..
         // aliasesFileName:别名文件名,其中行格式为
         // 别名:[tab]登录名
         // 构建词典 usersByLogin
....
    }// 生成器

     // 用户列表
  public Hashtable getUsersByLogin(){
    return usersByLogin;
  }

   // 错误
  public ArrayList getErreurs(){
    return erreurs;
  }
usersByLogin
字典(哈希表),其键为 passwd 文件中的登录名。与键关联的值是一个字符串数组(String [7]),其元素为 passwd 文件中与该登录名关联的行中的 7 个字段。如果该行少于 7 个字段,某些字段可能为空。
erreurs
错误消息列表——若无错误则为空

7.2. 该Web应用程序

我们计划构建以下Web应用程序(表单页面):

编号
名称
类型HTML
角色
1
cmbLogins
<select ...>...</select>
列出了所有可查询信息的登录账号
2
btnChercher
<input type="submit" ...>
用于启动搜索

当用户点击按钮 [Chercher] (2) 时,系统会向类型为 users 的 U 对象查询 (1) 中的登录信息。如果该登录信息存在,将返回以下响应(信息页面):

如上文浏览器中的 URL 所示,表单参数通过 GET 发送至服务器。 因此,我们可以直接向浏览器提供这个已配置的 URL。这就是我们在此处所做的,用于引入一个不存在的登录名。我们将得到以下响应(错误页面):

7.3. 应用程序架构

在此架构中,我们发现以下组件:

  • 视图:
    • logins.jsp,用于显示登录列表(视图 1)
    • infos.jsp,用于显示与登录相关的信息(视图 2)
    • erreurs.jsp,用于显示错误列表(视图 3)
  • 由以下操作使用的 ActionForm 类型表单:
    • formLogins 用于收集表单 logins.jsp 的数据
  • 操作:
    • SetupLoginAction 用于准备 formulaire.jsp 的内容,然后显示该视图
    • InfosLoginAction:在内容发送至服务器后处理 logins.jsp 的内容
    • ForwardAction:处理视图 infos.jsp erreurs.jsp 中的链接 [Retour vers le formulaire]
  • 业务类 users,供操作获取数据时使用
  • 由三个平面文件 passwdgroup aliases 提供的模型

7.4. Web 应用程序的配置文件

7.4.1. 文件 server.xml

应用程序上下文将命名为 /strutsquiest2。因此,需在 Tomcat 的 server.xml 文件中添加以下行:

    <Context path="/strutsquiest2" docBase="..." />

完成上述操作后,可能需要重启 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。 这将使我们能够获取两个初始化参数:passwdFileNamepasswd 文件的位置)和 groupFileNamegroup 文件的位置)。aliases 文件在此应用程序中没有用处。

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 所需的数据
  • 在用户提交(submit)第 1 号视图表单时,获取表单中的值

formLogins Bean 的结构与视图 1 的表单相关联。让我们来研究一下:

编号
名称
类型 HTML
角色
1
cmbLogins
<select ...>...</select>
列出了所有可查询信息的登录账号
2
btnChercher
<input type="submit" ...>
用于启动搜索

我们区分以下几种情况:

  • 从客户端到服务器时,对象 formLogins 用于存储上述表单 HTML 的值,该表单将通过按钮 [Envoyer] 提交。 因此,它需要一个字段 cmbLogins 来接收字段 HTML、cmbLogins、c.a.d 的值,即用户选择的登录名。
  • 从服务器到客户端,对象 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 字段名称相同的参数,即可初始化这些字段,如下例所示:

Image

  • 该请求包含参数 cmbLoginsafterpak)。因此,Struts 控制器将该参数的值复制到了 formLogins 表中的 cmbLogins 字段中。 随后执行了 SetupLoginsAction 操作,并最终显示了 logins.jsp 视图。该视图包含一个表单,其中某些字段的值来自 formLogins。 因此,名为 cmbLogins 的下拉选择字段 HTML 从 formLogins 中的字段 cmbLogins=afterpak)获取了其值。 这就是为什么登录名列表显示时,默认定位在登录名 afterpak 上。
  • 我们还可以尝试以如下方式传递参数 tLogins
http://localhost:8080/strutsquiest2/init.do?cmbLogins=afterpak&tLogins=login1&tLogins=login2

这将导致 formLogins 的字段 tLogins 被初始化为数组 {"login1","login2"}。 不过,我们稍后将看到,操作 SetupLoginsAction 会为字段 tLogins 赋值,并将这样创建的数组替换为一个新数组。因此,在视图 logins.jsp 中显示的正是这个新数组。

  • 前面的讨论虽然有些复杂,但其价值在于表明我们不能假设 /init 操作会在没有来自客户端的参数的情况下被触发。因此,使用 reset 方法来清空 formLogins 可能会很有用。 在这种情况下,我们需要派生类 DynaActionForm。但本文中我们并未这样做。
    • 调用 formLoginsreset 方法后,控制器会将客户端请求中的数据复制到 formLogins 中同名的字段中。 通常,/init 操作是在不带客户端参数的情况下调用的,但我们之前已经说明,没有任何东西能阻止客户端使用任意参数调用 /init 操作。因此,在此阶段结束时,cmbLogins tLogins 字段完全可能已赋值。 我们看到,字段 cmbLogins 会保留该值,但字段 tLogins 则不会。
    • 随后控制器会检查该操作的 validate 属性。在此处,其值为“false”。因此不会调用 formLoginsvalidate 方法。因此我们无需编写该方法。
    • 对象 SetupLoginsAction 会被创建,若已存在则会被回收,并调用其 execute 方法。其唯一作用是为 formLogins 的字段 tLogins 赋值。 该值为登录名数组,该数组将从业务类 users 中获取。此操作可能会失败。因此,/init 操作之后可能跟两个视图:
      • 如果 users 类无法提供登录表,则显示视图 erreurs.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 中的按钮 [Chercher] 时,通常会触发操作 /infosLogin。 此时会向服务器发送请求,该请求由视图中的 HTML <form> 标签确定:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
  • 可见该请求是通过 GET 方法发送至服务器的。因此用户可以手动输入:

Image

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

Image

  • 操作 /infosLogin 被调用时,传入的参数字符串为 cmbLogins=xx&tLogins=login1&tLogins=login2。 因此,formLogins 中的字段 cmbLogins tLogins 将分别接收值 "xx" 和 {"login1","login2"}。 操作 /infosLogin 将向业务类 users 查询与登录名 "xx" 相关的信息。users 类将返回该登录名不存在。因此显示了上文中的视图。现在,我们使用上文中的链接 [Retour au formulaire]:

Image

  • 链接 [Retour au formulaire] 触发的正是操作 /retourLogins。该操作仅直接显示视图 logins.jsp,不进行任何中间操作。 需要提醒的是,字段 tLogins 用于为视图 logins.jsp 的登录名列表提供数据。由于用户已将该值修改为 {"login1","login2"},因此列表中现在显示的就是这两个登录名。 再次强调,在应用程序的运行中,必须绝对考虑到用户或程序设置的任意参数的情况。解决此处提出的问题的方法是,让链接 [Retour au formulaire] 指向操作 /init。这样就能确保获得正确的登录名列表。
  • 让我们回到针对操作 /infosLogin 的常规请求,例如:

http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=afterpak

  • Struts 控制器将为对象 ActionForm 的字段 cmbLogins 赋值。而字段 tLogins 则不会被赋值(因为发送的请求中没有对应的字段)。这种行为符合我们的预期。 因此,我们无需为 formLogins 编写自定义的 reset 方法。
    • 一旦调用 formLoginsreset 方法,控制器会将客户端请求中的数据复制到 formLogins 中同名的字段中。 cmbLogins字段将接收一个值,即用户选择的登录名(afterpak)。
    • 随后,控制器检查该操作的 validate 属性。此处其值为“false”。因此不会调用 formLoginsvalidate 方法。
    • 对象 InfosLoginAction 被创建(若已存在则被回收),并调用其 execute 方法。其作用是获取与登录名 cmbLogins 相关的信息。这些信息将从业务类 users 中获取。此操作可能失败(例如登录名不存在)。 因此,操作 /infosLogin 之后可能包含两个视图:
      • users 类无法提供所需信息,则进入视图 erreurs.jsp
      • 否则,将显示视图 infos.jsp
    • 控制器将显示上述两个视图中的一个
    • 操作 /infosLogin 的请求-响应循环已完成。

操作 /retourLogins

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />
  • 操作 /retourLogins 由视图 erreurs.jsp infos.jsp 中的链接 [Retour au formulaire] 激活而触发。
  • 此处没有与该操作关联的表单。因此,系统会立即执行 ForwardAction 对象的 execute 方法,该方法将返回一个指向视图 /vues/logins.jspActionForward 对象。

7.4.6. 应用程序的消息文件

文件 struts-config.xml 的第三部分是消息文件:

        <message-resources 
      parameter="istia.st.struts.quiest.ApplicationResources"
    null="false"
  />        

文件 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;                        // 视图中显示的标题
String[] infosLogin;        // 视图中要显示的信息表

我们将在讲解类 InfosLoginAction 的代码时详细说明该类。

7.5.3. 视图 erreurs.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

Quiest2ActionServlet 继承自 Struts 控制器类 ActionServlet。 我们派生类 ActionServlet 来定制其 init 方法。实际上,该方法在 Servlet 初始加载时仅执行一次,将使我们能够构建一个 users 类型的业务对象。该对象实际上只需构建一次,而 init 方法是进行此构建的理想位置。 users对象的构建需要两个文件:passwdgroup。这两个文件的位置作为参数通过应用程序中的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 属性
  private users u = null;
  private ActionErrors erreurs = new ActionErrors();
  private String[] tLogins;

   //init
  public void init() throws ServletException {

    // 切勿忘记初始化父类
    super.init();

     // 局部变量
    final String[] initParams = {"passwdFileName", "groupFileName"};
    Properties params = new Properties();

    // 获取 Servlet 的初始化参数
    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 = "";
      }
       // 存储参数
      params.setProperty(initParams[i], valeur);
    } //for
     // 若初始化出现错误则返回
    if (erreurs.size() != 0) {
      return;
    }
     // 创建一个 users 对象
    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
     // 获取登录名列表
    tLogins = new String[u.getUsersByLogin().size()];
    Enumeration eLogins = u.getUsersByLogin().keys();
    for (int i = 0; i < tLogins.length; i++) {
      tLogins[i] = (String) eLogins.nextElement();
    }
     // 对登录名进行排序
    Arrays.sort(tLogins);
  } //初始化

   // 访问Servlet私有信息的方法
  public Object[] getInfos() {
    return new Object[] {erreurs, u, tLogins};
  }
}

简而言之,init方法的工作原理如下:

  • 首先,调用父类(ActionServlet)的 init 方法,使其正确初始化
  • 随后读取初始化参数。若参数缺失,则将私有属性 ActionErrors(错误)进行赋值。
  • 如果初始化参数齐全,则构建一个 users 对象。此构建过程可能会抛出异常。在这种情况下,将填充属性 ActionErrors erreurs
  • 如果构造成功,则从创建的对象中提取所有登录名的列表,并将其排序后存入一个数组中,该数组被存入私有属性 String[] tLogins
  • 生成的 users 对象存储在私有属性 users u 中。
  • 公共方法 getInfos 允许将三个私有属性(u、erreurs、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 {

     // 准备要显示的表单
     // 获取控制器Servlet的信息
     // 信息=(ActionErrors 错误, 用户 u, String[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // 初始化过程中是否出现错误?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // 将登录信息放入表单
    DynaActionForm formLogins=(DynaActionForm) form;
    formLogins.set("tLogins",infos[2]);
    return mapping.findForward("afficherLogins");
  }
}

与所有 Struts 动作一样,代码位于 execute 方法中。该方法:

  • 从 Struts 控制器中获取其通过 init 方法存储的信息。这是由 Action 类的 getServlet() 方法实现的。
  • 其中包括控制器错误属性 ActionErrors。如果该错误列表不为空,则将其放入请求中,并请求显示视图 erreurs.jsp
  • 如果错误列表为空,则将控制器最初创建的登录列表赋值给 Bean formLogins 的字段 tLogins。随后请求显示视图 logins.jsp,该视图将显示登录列表。

7.6.3. 类 InfosLoginBean 和 InfosLoginAction

操作 InfosLoginAction 的目的是检索与用户所选登录名相关联的信息,并将其呈现给用户。这些信息将被汇总到一个类型为 InfosLoginBean 的对象中:

package istia.st.struts.quiest;

public class InfosLoginBean implements java.io.Serializable{

   // Bean 包含信息页面所需的必要信息
  private String titre;
  private String[] infosLogin;

  // 构造函数
  public InfosLoginBean(String titre, String[] infosLogin){
    this.titre=titre;
    this.infosLogin=infosLogin;
  }

   // 获取器
  public String getTitre(){
    return this.titre;
  }
  public String[] getInfosLogin(){
    return this.infosLogin;
  }
  public String getInfosLogin(int i){
    return this.infosLogin[i];
  }
}

前一个类是一个 Bean,即 c.a.d。这是一个 Java 类,其中私有属性 T unAttribut 会自动关联到两个私有方法:

  • void setUnAttribut(T 值){unAttribut=值;}
  • T getUnAttribut(){ return unAttribut;}

请注意 getset 方法的特殊语法。如果属性是一个数组 T[] unAttribut,则可以为数组的元素创建 getset 方法:

  • void setUnAttribut(T 值, int i){unAttribut[i]=值;}
  • 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>

我们来看以下标签:

<bean:write name="infosLoginBean" scope="request" property="titre"/>

该语句要求将对象 infosLoginBean(名称)的“标题”(property)字段值写入查询(scope)中。待写入的值将通过 request.getAttribute("infosLoginBean") 获取。getTitre()。因此,InfosLoginBean类中必须存在getTitre方法。确实如此。标签

<bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/>

要求将请求中包含的 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 {

     // 需显示与登录相关的信息

     // 从控制器 Servlet 中获取信息
     // 信息=(ActionErrors 错误、用户 u、LoginBean[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // 初始化过程中是否出现错误?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // 首先获取该登录信息
    String login = (String) ( (DynaActionForm) form).get("cmbLogins");

    // 有什么问题吗?
    if (login == null) {
      // 不正常——请重新发送登录表单
            DynaActionForm formLogins=(DynaActionForm) form;
            formLogins.set("tLogins",infos[2]);
            return mapping.findForward("afficherLogins");
    }

     // 已获取登录凭据 - 正在查找
    String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);

     // 是否已找到?
    if (infosLogin == null) {
      // 未找到登录名 - 显示错误页面
      ActionErrors erreurs2=new ActionErrors();
      erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
      this.saveErrors(request, erreurs2);
      return mapping.findForward("afficherErreurs");
    }

     // 已找到登录名 - 将找到的信息放入请求中
    String titre="Application QuiEst - login["+login+"]";
    InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
    request.setAttribute("infosLoginBean",infosLoginBean);
    return mapping.findForward("afficherInfos");
  }

}

execute 方法的工作原理如下:

  • 首先获取 Struts 控制器在初始化时收集的信息。如果控制器检测到错误,则在此处停止执行并要求显示这些错误。
  • 验证是否存在登录名。如果用户通过登录名选择表单进行登录,则登录名已存在。但用户也可能直接在浏览器中输入操作的 URL,而不传递任何参数。如果不存在登录名,则重新显示登录名列表。
  • 若存在登录名,则向业务类 users 查询相关信息。若该类未找到所查询的登录名,则显示错误页面。 否则,将构建一个 InfosLoginBean 对象,用于存放视图 infos.jsp 所需的信息。该对象被放入请求中,并显示页面 infos.jsp

7.7. 部署

应用程序的结构如下:

 

7.8. 结论

我们在一个使用业务类的实际应用中应用了Struts。此外,我们还表明,必须特别关注客户端发送的请求,且绝不能对其性质做出任何假设。请求可能包含任意内容,因此任何应用程序都必须首先验证其有效性。