Skip to content

19. 案例研究 – 第1版

19.1. 模拟的 [业务] 层

让我们回顾一下我们正在构建的应用程序的架构:

我们拥有[业务、DAO、JPA]层的归档文件,并已列出了[Web]层需要了解的这些层中的要素。现在,我们可以使用Struts框架编写[Web]层了。

为了简化开发过程中的应用程序测试,我们将创建一个模拟的业务层,该层将实现 [business] 层的接口。架构将如下所示:

我们将基于模拟的[业务]层开发[Web]层。由于架构中不再包含数据库,测试将变得更加简单。 得益于 Spring 框架和接口的使用,在后续阶段将模拟的 [业务] 层替换为实际的 [业务、DAO、JPA] 架构时,不会对 [Web / Struts2] 层的代码产生任何影响。我们即将开发的 [Web / Struts2] 层可以原样使用。

我们将使用的模拟[业务]层如下:


package metier;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;
 
public class MetierSimule implements IMetier {
 
  // list of employees
  private Map<String, Employe> hashEmployes = new HashMap<String, Employe>();
  private List<Employe> listEmployes;
 
  // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
    // we retrieve employee n° SS
    Employe e = hashEmployes.get(SS);
    // a fictitious payslip is returned
    return new FeuilleSalaire(e, new Cotisation(3.49, 6.15, 9.39, 7.88), new ElementsSalaire(100, 100, 100, 100, 100));
  }
 
  // list of employees
  public List<Employe> findAllEmployes() {
    if (listEmployes == null) {
      // create a list of two employees
      listEmployes = new ArrayList<Employe>();
      listEmployes.add(new Employe("254104940426058", "Jouveinal", "Marie", "5 rue des oiseaux", "St Corentin", "49203", new Indemnite(2, 2.1, 2.1, 3.1, 15)));
      listEmployes.add(new Employe("260124402111742", "Laverti", "Justine", "La br�lerie", "St Marcel", "49014", new Indemnite(1, 1.93, 2, 3, 12)));
      // employee dictionary
      for (Employe e : listEmployes) {
        hashEmployes.put(e.getSS(), e);
      }
    }
    // we return the list of employees
    return listEmployes;
  }
}
  • 第 11 行:[MetierSimule] 类实现了 [IMetier] 接口,而该接口由实际的 [business] 层实现。
  • 第 14 行:一个以 INSEE 编号为索引的员工字典
  • 第 15 行:员工列表
  • 第 27–39 行:实现 [IMetier] 接口的 findAllEmployees 方法。
  • 第 30–33 行:创建包含两名员工的列表
  • 第 34–36 行:创建以 INSEE 编号为索引的员工字典
  • 第 18–24 行:实现 [IMetier] 接口中的 `calculerSalaire` 方法。此处返回一份虚构的工资单。

19.2. NetBeans 项目

NetBeans 项目结构如下:

  • 在 [1] 中:
  • [applicationContext.xml] 是 Spring 配置文件
  • [tiles.xml] 是名为 Tiles 的框架的配置文件
  • [web.xml] 是 Web 应用程序的配置文件
  • 在 [2] 中:应用程序的不同视图
  • 在 [3] 中:
  • [messages.properties]:消息文件
  • [struts.xml]:Struts 的配置文件
  • 在 [4] 中:应用程序的源代码。Struts 动作位于 [web.actions] 包中。
  • 在 [5] 中:模拟的 [business] 层
  • 在 [6] 中:所使用的压缩包。这里是各种工具的压缩包:Spring、Tiles、Struts 2、Struts 2/Spring 集成插件以及 Struts 2/Tiles 集成插件。
  • 在 [7] 中:实际的 [业务、DAO、JPA] 层的归档文件。它使我们能够访问 JPA 实体、[IMetier] 接口以及 [PayrollSheet] 和 [PayrollItems] 类。所有这些元素确实都被我们的 [SimulatedBusiness] 类所使用。

19.3. 项目配置

该项目通过以下文件进行配置:

  • [web.xml],用于配置 Web 应用程序
  • [struts.xml],用于配置 Struts 框架
  • [applicationContext.xml],用于配置 Spring 框架
  • [tiles.xml],用于配置 Tiles 框架

19.3.1. Web 应用程序配置

[web.xml] 文件内容如下:


<?xml version="1.0" encoding="UTF-8"?>
<web-app id="pam_struts_01" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <display-name>Pam</display-name>
    <!-- Tiles -->
  <context-param>
    <param-name> org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG </param-name>
    <param-value>/WEB-INF/tiles.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.apache.struts2.tiles.StrutsTilesListener</listener-class>
  </listener>
    <!-- Struts 2 -->
  <filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
     <!-- Spring -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>
  • 第 13–20 行:配置 Struts 2 过滤器——已见
  • 第 22–24 行:配置 Spring 监听器——之前已见过
  • 第 9–11 行:配置 Tiles 监听器。当 Web 应用程序启动时,将实例化 [org.apache.struts2.tiles.StrutsTilesListener]。随后它将使用其配置文件。该配置由第 5–8 行定义。因此,Tiles 配置文件即为 [WEB-INF/tiles.xml] 文件。

最终,当 Struts 应用程序启动时,将实例化三个类:

  • 一个用于 Struts 2 过滤器。这是处理 MVC 中“C”(控制器)的组件。
  • 另一个用于 Spring 监听器。Spring 将使用 [applicationContext.xml] 文件来实例化应用程序的 [业务、DAO、JPA] 层。与前面的示例一样,Spring 还将实例化一个 [Config] 类,该类将包含应用程序范围的数据。最后,Spring 会将对这个单一 [Config] 实例的引用注入到每个需要它的 Struts 动作中。
  • 再看一个 Tiles 监听器。该框架将负责视图管理。我们稍后会回到这一点。

19.3.2. Struts 框架配置

Struts 框架通过以下 [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>
  <!-- internationalization -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- spring integration -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />
 
 
  <!-- struts /Tiles actions -->
  <package name="default" namespace="/" extends="tiles-default">
    <!-- default action -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- action Form -->
    <action name="Formulaire" class="web.actions.Formulaire" method="input">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
    </action>
    <!-- action FaireSimulation -->
    <action name="FaireSimulation" class="web.actions.Formulaire" method="calculSalaire">
      <result name="success" type="tiles">simulation</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
    </action>
    <!-- action EnregistrerSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   <!-- action RetourFormulaire -->
    <action name="RetourFormulaire" >
      <result type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
   <!-- action VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
    </action>
   <!-- action RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer" method="execute">
      <result name="erreur" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   <!-- action TerminerSession -->
    <action name="TerminerSession" class="web.actions.Terminer" method="execute">
      <result name="success" type="redirectAction">
        <param name="actionName">Formulaire</param>
        <param name="namespace">/</param>
      </result>
    </action>
  </package>
 
</struts>

我们将随着讲解逐步探讨各种 Struts 动作。目前,请注意以下几点:

  • 第 8 行:定义消息文件
  • 第 10 行:定义了 Spring Bean 如何注入到 Struts 动作中。注入基于 Bean 的名称。需要由 Spring 初始化的 Struts 动作字段必须与待注入的 Bean 名称相同。
  • 第 25 行:定义了 [Form] 动作的“success”导航键对应的视图。我们可以看到,<result> 元素有一个我们不熟悉的 type='tiles' 属性。 我们熟悉的是 redirect 类型,它允许将客户端重定向到一个视图。在此,tiles 类型的视图由 Tiles 框架管理。tiles 类型在 [struts2-tiles-plugin-2.2.3.1.jar] 归档文件中的 [struts-plugin.xml] 文件中定义:


<struts>
    <package name="tiles-default" extends="struts-default">
        <result-types>
            <result-type name="tiles" class="org.apache.struts2.views.tiles.TilesResult"/>
        </result-types>
    </package>
</struts>

  • 第 3–5 行:Tiles 结果类型的定义。
  • 第 2 行:该类型定义在 [tiles-default] 包中,该包继承自 [struts-default] 包。

  • 第 14 行:定义了 [default] 包,该包将包含应用程序的所有操作。为了利用 Tiles 视图类型的定义,该包继承自 [tiles-default]。

19.3.3. Spring 框架配置

Spring 框架通过以下 [WEB-INF/applicationContext.xml] 文件进行配置:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
 
  <!-- application layers -->
 
<!-- web -->
  <bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- business -->
  <bean id="metier" class="metier.MetierSimule"/>
 
</beans>

  • 第 13 行:由 [business.SimulatedBusiness] 类实例化的模拟 [business] 层
  • 第 9–11 行:配置一个名为 config 的 Bean。与之前学习的示例一样,该 Bean 将用于封装应用程序范围的信息。与该 Bean 关联的类是以下 [Config] 类:


package web;
 
import java.util.List;
import jpa.Employe;
import metier.IMetier;
 
public class Config {
 
  // business layer initialized by Spring
  private IMetier metier;
  // list of employees
  private List<Employe> employes;
  // errors
  private Exception initException;
 
  // manufacturer
  public Config() {
  }
 
  // spring method for object initialization
  public void init() {
    // we ask for the list of employees
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
  }
 
  // getters and setters
  ...
}

让我们回到配置 Bean 的配置:


  <bean id="config" class="web.Config" init-method="init">
    <property name="metier" ref="metier"/>
  </bean>
  <!-- métier -->
  <bean id="metier" class="metier.Metier">
    ...  
</bean>

在第 2 行,我们可以看到第 5 行的业务 Bean 被注入(ref)到 [Config] 对象中名为 business(name)的字段中。该业务 Bean 是对 [business] 层的引用:

为了与 [business] 层交互,[web] 层中的所有 Struts 动作都需要对其进行引用。可以说,对 [business] 层的引用属于应用程序范围的数据。 所有用户的所有请求都需要它。这就是为什么我们将这个引用放在 [Config] 对象中。此外,在第 1 行,config Bean 的配置中包含一个 init-method 属性。该属性指定了 Bean 实例化后要执行的 Bean 方法。这里,我们指定在 [web.Config] 类实例化后,必须执行其 init 方法。该方法如下:


// business layer initialized by Spring
  private IMetier metier;
  // list of employees
  private List<Employe> employes;
  // errors
  private Exception initException;
 
  // manufacturer
  public Config() {
  }
 
  // spring method for object initialization
  public void init() {
    // we ask for the list of employees
    try {
      employes = metier.findAllEmployes();
    } catch (Exception ex) {
      initException = ex;
    }
  }

init 方法被执行时,该类的业务字段已由 Spring 实例化。因此,init 方法可以访问业务层接口 [IMetier](第 2 行):


package metier;
 
import java.util.List;
import jpa.Employe;
 
public interface IMetier {
  // get your payslip
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // list of employees
  List<Employe> findAllEmployes();
}
  • 第 8 行:该方法计算员工的工资
  • 第 10 行:该方法检索员工列表

我们可以看到init 方法从 [business] 层请求员工列表。该列表存储在第 4 行的字段中。如果发生异常,则将其存储在第 6 行的字段中。

综上所述,单个 [Config] 对象包含:

  • 对 [business] 层的引用
  • 员工列表

19.4. 生成 Tiles 视图

正如我们在 Struts 配置文件中所见,视图将由 Tiles 框架生成。我们将仅解释编写本应用程序所必需的内容。

Tiles 允许您从主页面生成视图。此页面(此处称为 [MasterPage.jsp])将由以下 JSP 片段组合而成:

Header.jsp
 
Input.jsp
 
Simulation.jsp
 
Simulations.jsp
 
异常.jsp
 
Error.jsp
 

这些 JSP 片段在 NetBeans 项目中定义如下:

Image

Tiles 框架允许我们定义哪些片段将被插入到主页面中。

主页面 [MasterPage.jsp] 如下所示:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
 
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link href="styles.css" rel="stylesheet" type="text/css"/>
    <title>
      <tiles:insertAttribute name="titre" ignore="true" />
    </title>
    <s:head/>
  </head>
  <body background="<s:url value="/ressources/standard.jpg"/>">
    <tiles:insertAttribute name="entete" />
    <hr/>
    <tiles:insertAttribute name="saisie" />
    <tiles:insertAttribute name="simulation" />
    <tiles:insertAttribute name="exception" />
    <tiles:insertAttribute name="erreur" />
    <tiles:insertAttribute name="simulations" />
  </body>
</html>

主页面是 JSP 片段的容器。在此,它由六个片段组成,即第 17 行至第 23 行中的内容。生成时,主页面中可能包含 0 到 6 个片段。此生成过程由 [WEB-INF/tiles.xml] 文件控制,该文件定义了 Tiles 视图:


<?xml version="1.0" encoding="UTF-8" ?>
 
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
 "http://tiles.apache.org/dtds/tiles-config_2_0.dtd">
 
<tiles-definitions>
 
<!-- the master page -->
  <definition name="masterPage" template="/MasterPage.jsp">
    <put-attribute name="entete" value="/Entete.jsp"/>
    <put-attribute name="titre"  value="Pam"/>
    <put-attribute name="saisie" value=""/>     
    <put-attribute name="simulation" value=""/>
    <put-attribute name="simulations" value=""/>
    <put-attribute name="exception" value=""/>     
    <put-attribute name="erreur" value=""/>     
  </definition>
 
<!-- input view -->
  <definition name="saisie" extends="masterPage">
    <put-attribute name="saisie"   value="/Saisie.jsp"/>     
  </definition>

  <!-- simulation view -->
 <definition name="simulation" extends="saisie">
    <put-attribute name="simulation"   value="/Simulation.jsp"/>     
  </definition>
 
<!-- the simulations view -->
  <definition name="simulations" extends="masterPage">
    <put-attribute name="simulations"   value="/Simulations.jsp"/>     
  </definition>
 
<!-- the exceptional view -->
  <definition name="exception" extends="masterPage">
    <put-attribute name="exception"   value="/Exception.jsp"/>     
  </definition>
 
<!-- error view -->
 <definition name="erreur" extends="masterPage">
    <put-attribute name="erreur"   value="/Erreur.jsp"/>     
  </definition>
</tiles-definitions>
  • 上述文件定义了六个名为 masterPage(第 9 行)、input(第 20 行)、simulation(第 25 行)、simulations(第 30 行)、exception(第 35 行)和 error(第 40 行)的 Tiles 视图。
  • 第 9–17 行:定义了一个名为 masterPagename)的视图,并将其与主页面 [MasterPage.jsp](template)关联。 我们看到该 JSP 页面定义了六个子视图。与主页面关联的 Tiles 视图必须指定与这六个子视图各自关联的 JSP 片段。我们可以看到,某些子视图被赋予了空字符串作为其值。这些子视图将不会被包含在主页面 [MasterPage.jsp] 中。因此,名为 masterPage 的 Tiles 视图仅由子片段 [Entete.jsp] 组成。
  • 第 20–22 行:定义了一个名为 `saisie` 的视图,该视图继承了之前定义的 `masterPage` 视图。这意味着它继承了 `masterPage` 视图中的所有定义。其定义等同于以下内容:

<definition name="saisie" template="/MasterPage.jsp">
    <put-attribute name="entete" value="/Entete.jsp"/>
    <put-attribute name="titre"  value="Pam"/>
    <put-attribute name="saisie" value=""/>     
    <put-attribute name="simulation" value=""/>
    <put-attribute name="simulations" value=""/>
    <put-attribute name="exception" value=""/>     
    <put-attribute name="erreur" value=""/>
    <put-attribute name="saisie" value="/Saisie.jsp"/>     
  </definition>

我们可以看到,它与 JSP 页面 [MasterPage.jsp] 相关联,因此必须定义该页面的六个子视图。我们可以看到第 9 行的定义覆盖了第 4 行的定义。因此,名为“saisie”的 Tiles 视图由 JSP 片段 [Entete.jsp, Saisie.jsp] 组成

若继续沿此思路推导,我们将得到下表:

Tiles视图
JSP 页面
主页面
Header.jsp
输入
Header.jsp、Input.jsp
模拟
Header.jspEntry.jspSimulation.jsp
simulations
Header.jspSimulations.jsp
异常
Header.jsp、Exception.jsp
错误
Header.jsp、Error.jsp

19.5. 消息文件

该应用程序已实现国际化。消息位于 [messages.properties] 和 [Formulaire.properties] 文件中。

[messages.properties] 文件内容如下:


Pam.titre=Calcul du salaire des assistantes maternelles
Pam.Erreurs.titre=Les erreurs suivantes se sont produites :
Pam.Erreurs.classe=Exception
Pam.Erreurs.message=Message
Pam.Erreur.libelle=L''erreur suivante s''est produite
Pam.Saisie.Heures.libell\u00e9=Heures travaill\u00e9es
Pam.Saisie.Jours.libell\u00e9=Jours travaill\u00e9s
Pam.Saisie.employ\u00e9=Employ\u00e9
Pam.BtnSalaire.libell\u00e9=Salaire
Pam.BtnEffacer.libell\u00e9=Effacer
Simulation.Infos.employe=Informations Employ\u00e9
Simulation.Employe.nom=Nom
Simulation.Employe.prenom=Pr\u00e9nom
Simulation.Employe.adresse=Adresse
Simulation.Employe.indice=Indice
Simulation.Employe.ville=Ville
Simulation.Employe.codePostal=Code Postal
Simulation.Infos.cotisations=Cotisations Sociales
Simulation.Cotisations.csgrds=CsgRds
Simulation.Cotisations.csgrds=Csgd
Simulation.Cotisations.retraite=Retraite
Simulation.Cotisations.secu=S\u00e9cu
Form.Infos.indemnites=Indemnit\u00e9s
Simulation.Indemnites.salaireHoraire=Salaire horaire
Simulation.Indemnites.entretienJour=Entretien/Jour
Simulation.Indemnites.repasJour=Repas/Jour
Simulation.Indemnites.cong\u00e9sPay\u00e9s=Cong\u00e9s pay\u00e9s
Simulation.Infos.Salaire=Salaire
Simulation.Salaire.salaireBase=Salaire de base
Simulation.Salaire.cotisationsSociales=Cotisations sociales
Simulation.Salaire.entretien=Indemnit\u00e9s d''entretien
Simulation.Salaire.repas=Indemnit\u00e9s de repas
Simulation.salaireNet=Salaire net
# formats
Format.heure = {0,time}
Format.nombre = {0,number,#0.0##}
Format.pourcent = {0,number,##0.00' %'}
Format.monnaie={0,number,##0.00' \u20ac'}
# liste des simulations
Pam.Simulations.titre=Liste des simulations
Pam.Simulations.num=Num\u00e9ro
Pam.Simulations.nom=Nom
Pam.Simulations.prenom=Pr\u00e9nom
Pam.Simulations.heures=Heures
Pam.Simulations.jours=Jours
Pam.Simulations.salairebase=Salaire de base
Pam.Simulations.indemnites=Indemnites
Pam.Simulations.cotisationsociales=Cotisations
Pam.Simulations.salairenet=Salaire
Pam.SimulationsVides.titre=La liste des simulations est vide
# menu
Menu.FaireSimulation=Faire la simulation
Menu.EffacerSimulation=Effacer la simulation
Menu.VoirSimulations=Voir les simulations
Menu.RetourFormulaire=Retour au formulaire de navigation
Menu.EnregistrerSimulation=Enregistrer la simulation
Menu.TerminerSession=Terminer la session
# msg d'erreur
Erreur.sessionexpiree=La session a expir\u00e9
Erreur.numSimulation=N\u00b0 de simulation incorrect
# erreur de conversion
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".

[Form.properties] 文件内容如下:


# pour que les doubles soient au format local
double.format={0,number,#0.00##}
# msg d'erreur
joursTravaill\u00e9s.error=Tapez un nombre entier compris entre 1 et 31
heuresTravaill\u00e9es.error=Tapez un nombre r\u00e9el entre 0 et 300

19.6. 样式表

Tiles 视图使用以下样式表 [styles.css]:


.libelle{
  background-color: #ccffff;
  font-family: 'Times New Roman',Times,serif;
  font-size: 14px;
  font-weight: bold;;
  padding-right: 5px;
  padding-left: 5px;
  padding-bottom: 5px;
  padding-top: 5px;
}
 
 
.info{
  background-color: #99cc00;;
  padding-right: 5px;
  padding-left: 5px;
  padding-bottom: 5px;
  padding-top: 5px;
}
 
.titreInfos{
  background-color: #ffcc00
}

19.7. 初始视图

为了探索该应用程序,我们将根据用户的各种操作来分析它。对于每项操作,我们将查看执行该操作的 Struts 操作以及作为响应返回的 Tiles 视图。

在 [struts.xml] 中,我们有以下操作:


  <!-- action par défaut -->
    <default-action-ref name="index" />
    <action name="index">
      <result type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
    </action>
    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
      <result name="simulation" type="tiles">simulation</result>
</action>

  • 第 2-8 行:应用程序的默认操作是 [Form!input]。

[Form] 类的定义如下:


package web.actions;
 
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
 
  // configuration initialized by Spring
  private Config config;
  // list of employees
  private List<Employe> employes;
  // error list
  private List<Erreur> erreurs;
  // payslip
  private FeuilleSalaire feuilleSalaire;
  // foreclosures
  private String comboEmployesValue;
  private Double heuresTravaillees;
  private Integer joursTravailles;
  // session
  private Map<String, Object> session;
  // menu
  private Menu menu;
 
  @Override
  public void prepare() throws Exception {
    ...
  }
 
  @Override
  public String input() {
  ....
  }
 
  // wage calculation
  public String calculSalaire() {
 ...
    }
  }
 
  @Override
  public void validate() {
  ...
  }
 
  @Override
  public void setSession(Map<String, Object> map) {
    session = map;
  }
 
  // getters and setters
  ...
}
  • 第 4 行:[Form] 动作实现了 Preparable 接口。该接口仅包含一个方法,即第 24 行中的 prepare 方法。该方法会在动作中的其他方法执行之前被调用一次。它通常用于初始化动作的模型。

操作模型定义在第 6–21 行:

  • 第 7 行:如前所述,config 字段由 Spring 初始化。它提供了对应用范围数据的访问:
  • 指向 [business] 层的引用
  • 员工列表的引用
  • 对 [Config] 对象实例化过程中可能发生的异常的引用
  • 第 9 行:员工列表。这将填充 [Saisie.jsp] 片段中的员工下拉列表。
  • 第 11 行:错误列表。这将填充 [Error.jsp] 片段。
  • 第 21 行:[Entete.jsp] 片段的菜单选项列表

在 [1] 中,显示菜单中的链接由 [Form] 操作的 menu 字段控制。

prepare 方法在 input 方法之前执行。具体如下:


  @Override
  public void prepare() throws Exception {
    // configuration error?
    Exception initException = config.getInitException();
    if (initException != null) {
      erreurs = new ArrayList<Erreur>();
      Throwable th = initException;
      while (th != null) {
        erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
        th = th.getCause();
      }
    } else {
      employes = config.getEmployes();
    }
}
  • 第 4 行:我们从 Spring 实例化的 [Config] 对象中获取异常
  • 第 5 行:如果在 [Config] 对象的实例化过程中发生了异常,则在第 11 行初始化错误列表。[Error] 类定义如下:

package web.entities;
 
import java.io.Serializable;
 
public class Erreur implements Serializable{
 
  public Erreur() {
  }
 
  // fields
  private String classe;
  private String message;
 
  // manufacturer
  public Erreur(String classe, String message){
    this.setClasse(classe);
    this.message=message;
  }
 
  // getters and setters
...
}

该类用于存储异常堆栈:

  • 第 11 行:异常类
  • 第 12 行:异常消息

让我们回到 prepare 方法:

  • 第 13 行:来自 [Config] 对象的员工列表被存储在操作的 employees 字段中。

执行完 prepare 方法后,接下来将执行 input 方法。具体如下:


  @Override
  public String input() {
    if (erreurs == null) {
      // menu
      menu = new Menu(true, false, false, true, false, true);
      return SUCCESS;
    } else {
      // menu
      menu = new Menu(false, false, false, false, false, false);
      return "exception";
    }
}

输入方法仅用于设置要显示的菜单选项列表。[Menu] 类定义如下:


package web.entities;
 
import java.io.Serializable;
 
public class Menu implements Serializable {
  // menu items
 
  private boolean faireSimulation;
  private boolean effacerSimulation;
  private boolean enregistrerSimulation;
  private boolean voirSimulations;
  private boolean retourFormulaire;
  private boolean terminerSession;
 
  public Menu() {
  }
 
  public Menu(boolean faireSimulation, boolean effacerSimulation, boolean enregistrerSimulation, boolean voirSimulations, boolean retourFormulaire, boolean terminerSession) {
    this.faireSimulation = faireSimulation;
    this.effacerSimulation = effacerSimulation;
    this.enregistrerSimulation = enregistrerSimulation;
    this.voirSimulations = voirSimulations;
    this.retourFormulaire = retourFormulaire;
    this.terminerSession = terminerSession;
  }
 
  // getters and setters
...  
}
  • 第 8–13 行:菜单中共有 6 个可能的链接
  • 第18–25行:该类的构造函数允许您指定应显示哪些链接以及不应显示哪些链接。

菜单链接显示在 [Entete.jsp] 中,这是一个存在于所有 Tiles 视图中的 JSP 片段。每个操作都将有一个菜单字段,用于控制 [Entete.jsp] 中菜单的显示。

让我们回到输入方法:


  @Override
  public String input() {
    if (erreurs == null) {
      // menu
      menu = new Menu(true, false, false, true, false, true);
      return SUCCESS;
    } else {
      // menu
      menu = new Menu(false, false, false, false, false, false);
      return "exception";
    }
}
  • 第3-6行:如果错误列表为空,则显示菜单 [运行仿真、查看仿真、结束会话],并返回输入键。
  • 第 9-10 行:如果错误列表不为空,则菜单将为空,并返回异常键。

让我们回到 [struts.xml] 中 [Form] 动作的配置:


    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
      <result name="simulation" type="tiles">simulation</result>
</action>
  • 第 5 行:input 键显示名为 input 的 Tiles 视图
  • 第 4 行:exception 键显示名为 exception 的 Tiles 视图

让我们从名为“input”的 Tiles 视图开始。它由 JSP 片段 [Entete.jsp] 和 [Saisie.jsp] 组成。

[Entete.jsp] 片段内容如下:

Image

其代码如下:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      <s:if test="menu.faireSimulation">
        |<a href="javascript:doSimulation()"><s:text name="Menu.FaireSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.effacerSimulation">
        |<a href="<s:url action="Formulaire!input"/>"><s:text name="Menu.EffacerSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.voirSimulations">
        |<a href="<s:url action="VoirSimulations"/>"><s:text name="Menu.VoirSimulations"/></a><br/>
      </s:if>
      <s:if test="menu.retourFormulaire">
        |<a href="<s:url action="RetourFormulaire"/>"><s:text name="Menu.RetourFormulaire"/></a><br/>
      </s:if>
      <s:if test="menu.enregistrerSimulation">
        |<a href="<s:url action="EnregistrerSimulation"/>"><s:text name="Menu.EnregistrerSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.terminerSession">
        |<a href="<s:url action="TerminerSession"/>"><s:text name="Menu.TerminerSession"/></a><br/>
      </s:if>
    </td>
  </tr>
</table>
  • 第 8-25 行:显示六个菜单链接 [运行模拟(第 8-10 行)、清除模拟(第 11-13 行)、查看模拟(第 14–16 行)、返回表单(第 17–19 行)、保存模拟(第 20–22 行)、结束会话(第 23–25 行)]。
  • 第 8、11、14、17、20、23 行:链接的显示由当前操作的菜单字段控制。

请注意,[Entete.jsp] 片段显示了一个 HTML 表格(第 4–28 行),但并非完整的 HTML 页面。请记住,应用程序的所有视图都嵌入在以下母版页 [MasterPage.jsp] 中:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
 
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link href="styles.css" rel="stylesheet" type="text/css"/>
    <title>
      <tiles:insertAttribute name="titre" ignore="true" />
    </title>
    <s:head/>
  </head>
  <body background="<s:url value="/ressources/standard.jpg"/>">
    <tiles:insertAttribute name="entete" />
    <hr/>
    <tiles:insertAttribute name="saisie" />
    <tiles:insertAttribute name="simulation" />
    <tiles:insertAttribute name="exception" />
    <tiles:insertAttribute name="erreur" />
    <tiles:insertAttribute name="simulations" />
  </body>
</html>

[Entete.jsp] 片段被插入到第 17 行,位于一个常规的 HTML 页面中。

[Saisie.jsp] 片段插入在第 19 行。以下是生成的视图:

Image

[Saisie.jsp] 片段的代码如下:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
 
 
<script language="javascript" type="text/javascript">
  function doSimulation(){
    // mail the form
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  }  
</script>
 
<!-- data entry -->
<s:form name="Saisie" id="Saisie">
  <s:select name="comboEmployesValue" list="employes" listKey="SS" listValue="prenom+' ' +nom" key="Pam.Saisie.employé"/>
  <s:textfield name="heuresTravaillees" key="Pam.Saisie.Heures.libellé" value="%{#parameters['heuresTravaillees']!=null ? #parameters['heuresTravaillees'] : heuresTravaillees==null ? '' : getText('double.format',{heuresTravaillees})}"/>
  <s:textfield name="joursTravailles" key="Pam.Saisie.Jours.libellé" value="%{#parameters['joursTravailles']!=null ? #parameters['joursTravailles'] : joursTravailles==null ? '' : joursTravailles}"/>
  <input type="hidden" name="action"/>
</s:form>
  • 第 17 行:表单没有 action 属性。默认情况下,action='Form'
  • 第 18 行:显示员工下拉列表。下拉列表的内容(list 属性)由当前操作的 employees 字段提供。选项的 value 属性将是员工的社保号(listKey 属性)。每个选项显示的标签将是员工的姓名(listValue 属性)。 在组合框中选中的员工的社会保障号码将提交至 [Form].comboEmployeesValue 字段(name 属性)。
  • 第 19 行:工作小时数输入字段。显示值(value 属性)取自 [Form] 操作中的 heuresTravaillees 字段,格式如下(Form.properties):

double.format={0,number,#0.00##}

该值将发布到 [Form].hoursWorked 字段(名称属性)。

  • 第 20 行:用于输入工作天数的字段。显示值(value 属性)是 [Form] 操作中 daysWorked 字段的值。

该值将提交至 [Form].daysWorked 字段(name 属性)。

最终,在启动时若无错误,显示的“磁贴”视图如下:

Image

让我们回到 [Form] 操作的配置:


    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
      <result name="simulation" type="tiles">simulation</result>
</action>

我们已经看到,[Form].input 操作也可以在第 4 行触发 exception 键。在这种情况下,名为 exception 的 Tiles 视图会被显示出来。该视图由 [Header.jsp] 和 [Exception.jsp] 片段组成。我们已经介绍了 [Header.jsp] 片段。[Exception.jsp] 片段如下:

Image

这是应用程序第 2 版的启动页面,此时数据库管理系统 (DBMS) 尚未启动。[Error.jsp] 片段的 JSP 代码如下:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<h2><s:text name="Pam.Erreurs.titre"/></h2>
<table>
  <tr class="titreInfos">
    <th><s:text name="Pam.Erreurs.classe"/></th>
    <th><s:text name="Pam.Erreurs.message"/></th>
  </tr>
  <s:iterator value="erreurs">
    <tr>
      <td class="libelle"><s:property value="classe"/></td>
      <td class="info"><s:property value="message"/></td>
    </tr>
  </s:iterator>
</table>
  • 第 10–14 行:一个遍历 [Form] 操作中错误的 List<Error> 集合的迭代器。请注意,发生错误时,那里会存储一个异常堆栈。

19.8. 运行模拟

初始视图显示后,您可以通过 [运行模拟] 链接计算薪资。

19.8.1. 验证输入内容

请参考以下操作流程:

  • 在 [1] 中,一个错误的输入
  • 在 [2] 中,发送的响应。

让我们来看看 [struts.xml] 中 [Form] 动作的配置:


    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="success" type="tiles">saisie</result>
      <result name="exception" type="tiles">exception</result>
      <result name="input" type="tiles">saisie</result>
      <result name="simulation" type="tiles">simulation</result>
</action>

我们知道,在发生验证错误时,验证拦截器会返回 input 键。因此,系统会返回 Tiles 的输入视图。验证过程确保带有错误的字段会显示相应的错误信息。

[Form] 操作的验证由以下 [Form-validation.xml] 文件处理:


<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
 
 
<validators>
 
  <field name="heuresTravaillees" >
    <field-validator type="required" short-circuit="true">
      <message key="heuresTravaillées.error"/>
    </field-validator>
 
    <field-validator type="conversion" short-circuit="true">
      <message key="heuresTravaillées.error"/>
    </field-validator>
 
    <field-validator type="double" short-circuit="true">
      <param name="minInclusive">0</param>
      <param name="maxInclusive">300</param>
      <message key="heuresTravaillées.error"/>
    </field-validator>
  </field>
 
  <field name="joursTravailles" >
    <field-validator type="required" short-circuit="true">
      <message key="joursTravaillés.error"/>
    </field-validator>
 
    <field-validator type="conversion" short-circuit="true">
      <message key="joursTravaillés.error"/>
    </field-validator>
 
    <field-validator type="int" short-circuit="true">
      <param name="min">0</param>
      <param name="max">31</param>
      <message key="joursTravaillés.error"/>
    </field-validator>
  </field>
 
</validators>
  • 第 6–20 行验证 hoursWorked 字段是否为 [0,300] 范围内的实数。
  • 第 22–36 行验证 `joursTravaillés` 字段是否为 [0,31] 范围内的整数。

让我们回到 [struts.xml] 中 [Form] 动作的配置:


    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      ...
      <result name="input" type="tiles">saisie</result>
</action>

我们知道,如果发生验证错误,验证拦截器会返回“input”键。因此,将返回“Input” Tiles视图。

回顾一下,该视图由 [Header.jsp] 和 [Input.jsp] 两个片段组成,其中 [Header.jsp] 包含标题和选项列表,而 [Input.jsp] 包含输入表单。当发生输入错误时,验证过程会确保错误字段显示错误信息,并展示其错误的值。 [Header.jsp] 片段在验证过程中不发挥任何作用。让我们看看它的代码:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      <s:if test="menu.faireSimulation">
        |<a href="javascript:doSimulation()"><s:text name="Menu.FaireSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.effacerSimulation">
        |<a href="<s:url action="Formulaire!input"/>"><s:text name="Menu.EffacerSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.voirSimulations">
        |<a href="<s:url action="VoirSimulations"/>"><s:text name="Menu.VoirSimulations"/></a><br/>
      </s:if>
      <s:if test="menu.retourFormulaire">
        |<a href="<s:url action="RetourFormulaire"/>"><s:text name="Menu.RetourFormulaire"/></a><br/>
      </s:if>
      <s:if test="menu.enregistrerSimulation">
        |<a href="<s:url action="EnregistrerSimulation"/>"><s:text name="Menu.EnregistrerSimulation"/></a><br/>
      </s:if>
      <s:if test="menu.terminerSession">
        |<a href="<s:url action="TerminerSession"/>"><s:text name="Menu.TerminerSession"/></a><br/>
      </s:if>
    </td>
  </tr>
</table>

这六个链接由模板的菜单字段配置(第 8、11、14、17、20、23 行)。当发生错误时,该模板不会被操作更新,导致页面没有菜单。为了解决此问题,[Form] 类提供了以下 validate 方法:


package web.actions;
 
import com.opensymphony.xwork2.ActionSupport;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
 
  ...
  // menu
  private Menu menu;
 
  @Override
  public void prepare() throws Exception {
  ...
  }
 
  @Override
  public String input() {
  ...
  }
 
  // wage calculation
  public String calculSalaire() {
  ...
  }
 
  @Override
  public void validate() {
    // mistakes?
    if (!getFieldErrors().isEmpty()) {
      // menu
      menu = new Menu(true, false, false, true, false, true);
    }
  }
 
  // getters and setters
  ...
}
  • 第 27 行:我们知道,当此代码存在时,验证过程会执行 validate 方法。我们借此机会更新第 4 行中的菜单,该菜单属于 [Entete.jsp] 片段模板的一部分。
  • 第 29–32 行:如果存在验证错误,则将菜单设置为重新显示 Tiles 输入视图。如果不存在验证错误,则不执行任何操作。随后,`calculSalaire` 方法负责创建待显示的视图模型。

19.8.2. 薪资计算

让我们回到页眉中的 JSP 代码:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      <s:if test="menu.faireSimulation">
        |<a href="javascript:doSimulation()"><s:text name="Menu.FaireSimulation"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>
  • 第 9 行:当用户点击 [运行模拟] 链接时,将执行 JavaScript 函数 doSimulation。该函数在 [Saisie.jsp] 片段中定义:

<script language="javascript" type="text/javascript">
  function doSimulation(){
    // mail the form
    document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
    document.forms['Saisie'].submit();
  }
</script>
 
<!-- data entry -->
<s:form name="Saisie" id="Saisie">
  <s:select name="comboEmployesValue" list="employes" listKey="SS" listValue="prenom+' ' +nom" key="Pam.Saisie.employé"/>
  <s:textfield name="heuresTravaillees" key="Pam.Saisie.Heures.libellé" value="%{#parameters['heuresTravaillees']!=null ? #parameters['heuresTravaillees'] : heuresTravaillees==null ? '' : getText('double.format',{heuresTravaillees})}"/>
  <s:textfield name="joursTravailles" key="Pam.Saisie.Jours.libellé" value="%{#parameters['joursTravailles']!=null ? #parameters['joursTravailles'] : joursTravailles==null ? '' : joursTravailles}"/>
  <input type="hidden" name="action"/>
</s:form>
  • 第 14 行:一个名为 action 的隐藏字段将被提交至 [Form] 操作。该字段允许我们指定表单提交时要执行的操作和方法。正如我们在前几个示例中看到的,这些可以通过名为 action:Action!method 的参数来指定。该参数的具体值并不重要,关键在于它必须存在。
  • 第 2–6 行:当用户点击 [Header.jsp] 片段中的 [Run Simulation] 链接时执行的 JavaScript 函数。
  • 第 4 行:我们修改隐藏的 action 字段的 name 属性。确保其符合 Struts 预期的 action:Action!method 格式。
  • 第 5 行:提交名为“Saisie de la ligne 5”的表单。因此,将提交以下参数字符串:
comboEmployesValue=SS1&heuresTravaillees=xx&joursTravailles=yy&action:Formulaire!calculSalaire

SS1:下拉框中选定员工的INSEE编号

heuresTravaillees:工作小时数

daysWorked:工作日数

action:Form!calculateSalary:上述元素将传递至 [Form] 操作,随后该操作的 calculateSalary 方法将被执行。

[Form].calculateSalary 方法如下:


// wage calculation
  public String calculSalaire() {
    try {
      // salary calculation
      feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
      // put the simulation in the session
      session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
      // menu
      menu = new Menu(true, true, true, true, false, true);
      // finish
      return "simulation";
    } catch (Throwable th) {
      ...
    }
  }
  • 第 5 行:向 [business] 层请求薪资计算
  • 第 7 行:向用户的会话中添加了一个 Simulation 对象。这是因为后续请求可能需要它。[Simulation] 类如下所示:

package web.entities;
 
import java.io.Serializable;
import metier.FeuilleSalaire;
 
public class Simulation implements Serializable{
 
  public Simulation() {
  }
 
  // simulation fields
  private Integer num;
  private FeuilleSalaire feuilleSalaire;
  private String heuresTravaillées;
  private String joursTravaillés;
 
  // manufacturer
  public Simulation(Integer num,String heuresTravaillées, String joursTravaillés, FeuilleSalaire feuilleSalaire){
    this.setNum(num);
    this.setFeuilleSalaire(feuilleSalaire);
    this.setHeuresTravaillées(heuresTravaillées);
    this.setJoursTravaillés(joursTravaillés);
  }
 
  public double getIndemnites(){
    return feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas();
  }
 
  // getters and setters
...
}
  • 第 12 行:模拟编号。每次保存新的模拟时,该编号会递增。
  • 第 13 行:员工的工资单
  • 第14行:工作小时数
  • 第15行:工作天数
  • 第 25 行:getIndemnites 方法返回该员工的总补偿金额

我们将看到,[Simulation] 类是 [Simulations.jsp] 片段的模型,该片段用于显示所有已执行的模拟。

回到 [Form].calculateSalary 方法:


// wage calculation
  public String calculSalaire() {
    try {
      // salary calculation
      feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
      // put the simulation in the session
      session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
      // menu
      menu = new Menu(true, true, true, true, false, true);
      // finish
      return "simulation";
    } catch (Throwable th) {
 ...
  }
  • 第 9 行:更新菜单
  • 第 11 行:返回“模拟”导航键。

返回 [表单] 操作配置:


    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
         ...
      <result name="simulation" type="tiles">simulation</result>
</action>

第 4 行表明,“simulation”导航键会显示名为“simulation”的 Tiles 视图。该视图由以下 JSP 片段组成:[Header, Input, Simulation]。

渲染后的视图如下:

  • 在 [1] 中,片段 [Header.jsp]
  • 在 [2] 中,片段 [Input.jsp]
  • 在 [3] 处,片段 [Simulation.jsp]。请注意,显示的工资单是由 [business] 层渲染的虚构工资单。

前两个片段已经介绍过了。[Simulation.jsp] 片段如下:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<hr/>
<!-- information Employees -->
<span class="titreInfos">
  <s:text  name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
  <!-- line 1 -->
  <tr>
    <th class="libelle">
      <s:text name="Simulation.Employe.nom"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Employe.prenom"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Employe.adresse"/>
    </th>
  </tr>
  <!-- line 2 -->
  <tr>
    <td class="info">   
      <s:property value="feuilleSalaire.employe.nom"/>      
    </td>
    <td class="info">
      <s:property value="feuilleSalaire.employe.prenom"/>
    </td>
    <td class="info">
      <s:property value="feuilleSalaire.employe.adresse"/>
    </td>
</table>
<table>
  <!-- line 1 -->
  <tr>
    <th class="libelle"><s:text name="Simulation.Employe.ville"/></th>
    <th class="libelle">
      <s:text name="Simulation.Employe.codePostal"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Employe.indice"/>
    </th>
  </tr>
  <!-- line 2 -->
  <tr>
    <td class="info">
      <s:property value="feuilleSalaire.employe.ville"/>
    </td>
    <td class="info">
      <s:property value="feuilleSalaire.employe.codePostal"/>
    </td>
    <td class="info">
      <s:property value="feuilleSalaire.employe.indemnite.indice"/>
    </td>
</table>
<!-- information Contributions -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.cotisations"/>
</span>
 
<br/><br/>
<table>
  <!-- line 1 -->
  <tr>
    <th class="libelle">
      <s:text name="Simulation.Cotisations.csgrds"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Cotisations.csgrds"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Cotisations.retraite"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Cotisations.secu"/>
    </th>
  </tr>
  <!-- line 2 -->
  <tr>
    <td class="info">
      <s:text name="Format.pourcent">
        <s:param value="feuilleSalaire.cotisation.csgrds"/>
      </s:text>
    </td>
    <td  class="info">
      <s:text name="Format.pourcent">
        <s:param value="feuilleSalaire.cotisation.csgd"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.pourcent">
        <s:param value="feuilleSalaire.cotisation.retraite"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.pourcent">
        <s:param value="feuilleSalaire.cotisation.secu"/>
      </s:text>
    </td>
</table>
<!-- information Indemnities -->
<br/>
<span class="titreInfos">
  <s:text name="Form.Infos.indemnites"/>
</span>
<br/><br/>
<table>
  <!-- line 1 -->
  <tr>
    <th class="libelle">
      <s:text name="Simulation.Indemnites.salaireHoraire"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Indemnites.entretienJour"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Indemnites.repasJour"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Indemnites.congésPayés"/>
    </th>
  </tr>
  <!-- line 2 -->
  <tr>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.employe.indemnite.baseHeure"/>
      </s:text>
    </td>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.employe.indemnite.entretienJour"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.employe.indemnite.repasJour"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.employe.indemnite.indemnitesCP"/>
      </s:text>
    </td>
  </tr>
</table>
<!-- salary information -->
<br/>
<span class="titreInfos">
  <s:text name="Simulation.Infos.Salaire"/>
</span>
<br/><br/>
<table>
  <!-- line 1 -->
  <tr>
    <th class="libelle">
      <s:text name="Simulation.Salaire.salaireBase"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Salaire.cotisationsSociales"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Salaire.entretien"/>
    </th>
    <th class="libelle">
      <s:text name="Simulation.Salaire.repas"/>
    </th>
  </tr>
 
  <!-- line 2 -->
  <tr>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.salaireBase"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.cotisationsSociales"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.indemnitesEntretien"/>
      </s:text>
    </td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.indemnitesRepas"/>
      </s:text>
    </td>
  </tr>
</table>
<!-- Net salary-->
<br/>
<table>
  <tr>
    <td class="libelle">
      <s:text name="Simulation.salaireNet"/>
    <td></td>
    <td class="info">
      <s:text name="Format.monnaie">
        <s:param value="feuilleSalaire.elementsSalaire.salaireNet"/>
      </s:text>
    </td>
  </tr>
</table>

代码虽然长……但功能很简单。这段代码展示了 [Form].paystub 字段的各种属性,该字段代表员工的工资单。

回到 [Form].calculatePay 方法:


// wage calculation
  public String calculSalaire() {
    try {
      ...
      return "simulation";
    } catch (Throwable th) {
      erreurs = new ArrayList<Erreur>();
      while (th != null) {
        erreurs.add(new Erreur(th.getClass().getName(), th.getMessage()));
        th = th.getCause();
      }
      // menu
      menu = new Menu(false, false, false, false, true, true);
      return "exception";
    }
  }

薪资计算可能会出错。例如,如果与数据库管理系统(DBMS)的连接失败,就会出现这种情况。在这种情况下,我们会处理发生的异常。我们在学习 [Form] 方法时已经遇到过这种情况。

  • 第 7–11 行:我们根据异常堆栈创建一个 Error 对象列表
  • 第13行:设置菜单
  • 第14行:设置异常键。

异常键将显示 Tiles 异常视图:


    <!-- action Formulaire -->
    <action name="Formulaire" class="web.actions.Formulaire">
      <result name="exception" type="tiles">exception</result>
      ...
</action>

此 Tiles 视图已显示。其外观如下:

Image

19.9. 保存模拟

运行模拟后,用户可能希望将其保存到会话中。

  • 在[1]中,模拟被保存
  • 在[2]中,响应界面显示已执行的模拟列表,并将新模拟添加到其中

[保存模拟]链接位于[Entete.jsp]片段中:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.enregistrerSimulation">
        |<a href="<s:url action="EnregistrerSimulation"/>"><s:text name="Menu.EnregistrerSimulation"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>

我们可以看到,点击链接会触发 [SaveSimulation] 操作。该操作在 [struts.xml] 文件中的配置如下:


   <!-- action EnregistrerSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
</action>
  • 第 1 行:[SaveSimulation] 操作与 [Save] 类及其 execute 方法相关联。

[Save] 类如下所示:


package web.actions;
 
...
public class Enregistrer extends ActionSupport implements SessionAware {
 
  // session
  private Map<String, Object> session;
  // menu
  private Menu menu;
 
  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
 
  // action execution
  public String execute() {
    // retrieve the last simulation in the session
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    ...
  }
 
  // getters and setters
  ...
}
  • 第 4 行:由于该 Action 需要访问会话,因此它实现了 SessionAware 接口。
  • 第 7 行:会话
  • 第 9 行:菜单

当 [Save] 操作被实例化时,会调用其 execute 方法。请注意,该方法的职责是将最新的模拟结果放入会话中。该模拟结果将被添加到已完成的模拟列表中,该列表同样存储在会话中。

  • 第 19 行:我们检索会话中存储的最后一次模拟。
  • 第 20–22 行:如果未找到,则会话很可能已过期。实际上,会话仅持续一定时间,该时间可在配置应用程序的 [web.xml] 文件中设置。
  • 第 21 行:我们返回错误键。

回到 [SaveSimulation] 操作的配置:


   <!-- action EnregistrerSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
</action>

我们可以看到,“error”键(第3行)触发了名为“error”的 视图的显示。该视图由片段[Entete.jsp]和[Erreur.jsp]组成,外观如下:

Image

[Erreur.jsp] 片段内容如下:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<h2><s:text name="Pam.Erreur.libelle"/></h2>
<h4><s:text name="Erreur.sessionexpiree"/></h4>

回到 [Save].execute 方法:


// action execution
  public String execute() {
    // retrieve the last simulation in the session
    Simulation simulation = (Simulation) session.get("simulation");
    if (simulation == null) {
      return ERROR;
    }
    // retrieve the number of the last simulation
    Integer numDerniereSimulation = (Integer) session.get("numDerniereSimulation");
    if (numDerniereSimulation == null) {
      numDerniereSimulation = 0;
    }
    // increment it
    numDerniereSimulation++;
    // we give it the new number in the session
    session.put("numDerniereSimulation", numDerniereSimulation);
    // retrieve the list of simulations
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      simulations = new ArrayList<Simulation>();
      session.put("simulations", simulations);
    }
    // we add the current simulation
    simulation.setNum(numDerniereSimulation);
    simulations.add(simulation);
    // the list of simulations is displayed
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }
  • 第 9–16 行:各种模拟从 1 开始编号。最后分配的编号存储在会话中,键名为 numDerniereSimulation。第 9–16 行的代码检索该键,并递增与其关联的值。
  • 第 18–22 行:模拟列表存储在会话中,键名为 simulations。第 18–22 行会检索该列表(若存在)或创建该列表(若不存在)。
  • 第 24–25 行:获取模拟列表后,将当前模拟添加到其中(第 25 行)。此前,已为当前模拟分配了一个编号(第 24 行)。
  • 第 27 行:设置待显示的菜单
  • 第 28 行:返回“simulations”导航键。

返回 [struts.xml] 中 [EnregistrerSimulation] 操作的配置:


   <!-- action EnregistrerSimulation -->
    <action name="EnregistrerSimulation" class="web.actions.Enregistrer" method="execute">
      <result name="error" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
</action>

第 4 行:"simulations" 键会触发显示名为 "simulations" 的 Tiles 视图。该视图由 [Entete.jsp] 和 [Simulations.jsp] 片段组成。显示的视图如下:

  • 在 [1] 处,是 [Entete.jsp] 片段,我们现在已经很熟悉了。
  • 在 [2] 处,是片段 [Simulations.jsp]

[Simulations.jsp]片段如下:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<!-- empty simulation list -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- non-empty simulation list -->
<s:if test="#session['simulations'].size()!=0">
  <h2><s:text name="Pam.Simulations.titre"/></h2>
  <table>
    <tr class="titreInfos">
      <th><s:text name="Pam.Simulations.num"/></th>
      <th><s:text name="Pam.Simulations.nom"/></th>
      <th><s:text name="Pam.Simulations.prenom"/></th>
      <th><s:text name="Pam.Simulations.heures"/></th>
      <th><s:text name="Pam.Simulations.jours"/></th>
      <th><s:text name="Pam.Simulations.salairebase"/></th>
      <th><s:text name="Pam.Simulations.indemnites"/></th>
      <th><s:text name="Pam.Simulations.cotisationsociales"/></th>
      <th><s:text name="Pam.Simulations.salairenet"/></th>
    </tr>
    <s:iterator value="#session['simulations']">
      <s:url action="SupprimerSimulation" var="url">
        <s:param name="id" value="num"/>
      </s:url>
      <tr>
        <td class="libelle"><s:property value="num"/></td>
        <td class="info"><s:property value="feuilleSalaire.employe.nom"/></td>
        <td class="info"><s:property value="feuilleSalaire.employe.prenom"/></td>
        <td class="info"><s:property value="heuresTravaillées"/></td>
        <td class="info"><s:property value="joursTravaillés"/></td>
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="feuilleSalaire.elementsSalaire.salaireBase"/>
          </s:text>
        </td>
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="indemnites"/>
          </s:text>
        </td>
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="feuilleSalaire.elementsSalaire.cotisationsSociales"/>
          </s:text>
        </td>
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="feuilleSalaire.elementsSalaire.salaireNet"/>
          </s:text>
        </td>
        <td class="info"><a href="<s:property value="#url"/>">Retirer</a></td>
      </tr>
    </s:iterator>
  </table>
</s:if>
  • 第 5-7 行:如果会话中没有模拟,则显示以下视图:

Image

  • 第13-21行:显示表格列标题

Image

  • 第 23-55 行:遍历会话中找到的模拟列表
  • 第 24-26 行:创建一个名为 urlid 属性)的 URL。该 URL 生成的 HTML 链接如下:
<a href="<a href="view-source:http://localhost:8084/pam/SupprimerSimulation.action?id=1">/pam/SupprimerSimulation.action?id=1</a>">Retirer</a>

我们可以看到,该链接指向 [DeleteSimulation] 操作,并带有一个 id 参数,该参数代表要从列表中删除的模拟的编号。

  • 第 28–54 行:在遍历模拟列表的每次迭代中,都会显示当前模拟的属性。

Image

19.10. 删除模拟

用户可能希望从模拟列表中删除某个模拟:

  • 在[1]中,已删除模拟#1
  • 在[2]中,已移除模拟#1

[删除] 链接位于我们之前已经查看过的 [Simulations.jsp] 片段中:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<!-- empty simulation list -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
  <h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- non-empty simulation list -->
<s:if test="#session['simulations'].size()!=0">
  <h2><s:text name="Pam.Simulations.titre"/></h2>
  <table>
    <tr class="titreInfos">
      ...
    </tr>
    <s:iterator value="#session['simulations']">
      <s:url action="SupprimerSimulation" var="url">
        <s:param name="id" value="num"/>
      </s:url>
      <tr>
        ...
        <td class="info">
          <s:text name="Format.monnaie">
            <s:param value="feuilleSalaire.elementsSalaire.salaireNet"/>
          </s:text>
        </td>
        <td class="info"><a href="<s:property value="#url"/>">Retirer</a></td>
      </tr>
    </s:iterator>
  </table>
</s:if>
  • 第 16-18 行:生成 HTML 链接
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?id=</a>num">Retirer</a>

其中 num 是要删除的模拟编号。

[DeleteSimulation] 操作在 [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>
  <!-- internationalization -->
  <constant name="struts.custom.i18n.resources" value="messages" />
  <!-- spring integration -->
  <constant name="struts.objectFactory.spring.autoWire" value="name" />
 
 
  <!-- struts /Tiles actions -->
  <package name="default" namespace="/" extends="tiles-default">
    ...
   <!-- action RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      <result name="erreur" type="tiles">erreur</result>
      <result name="simulations" type="tiles">simulations</result>
    </action>
   ...
  </package>
 
    <!-- Add packages here -->
 
</struts>
  • 第 16 行:[SupprimerSimulation] 操作与 [Supprimer] 类相关联。由于未指定具体方法,因此将执行其 execute 方法。[Supprimer] 类的定义如下:

package web.actions;
 
...
public class Supprimer extends ActionSupport implements SessionAware {
 
  // session
  private Map<String, Object> session;
  // id of simulation to be deleted
  private String id;
  // menu
  private Menu menu;
 
  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
 
  // action execution
  public String execute() {
    // simulations are retrieved from the
    List<Simulation> simulations = (List<Simulation>) session.get("simulations");
    if (simulations == null) {
      // abnormal case - session must have expired
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // id test
    int num = 0;
    boolean erreur = false;
    try {
      num = Integer.parseInt(id);
      erreur = num <= 0;
    } catch (NumberFormatException ex) {
      // abnormal
      erreur = true;
    }
    // mistake?
    if (erreur) {
      menu = new Menu(false, false, false, false, true, true);
      return "erreur";
    }
    // search for the simulation to be deleted
    for (int i = 0; i < simulations.size(); i++) {
      if (num == simulations.get(i).getNum()) {
        simulations.remove(i);
        break;
      }
    }
    // the list of simulations is displayed
    menu = new Menu(false, false, false, false, true, true);
    return "simulations";
  }
 
  // getters and setters
...
}
  • 第 4 行:[Delete] 操作实现了 [SessionAware] 接口以访问会话。
  • 第 7 行:会话
  • 第 9 行:待删除的模拟编号。请注意,我们是通过 HTML URL 来实例化 [Delete] 类的:
<a href="<a href="view-source:http://localhost:8084/pam-01/SupprimerSimulation.action?id=2">/pam-01/SupprimerSimulation.action?id=</a>num">Retirer</a>

其中 num 是要删除的模拟的编号。该编号将存储在第 9 行的 id 字段中。

  • 第 11 行:响应请求时将显示的视图菜单
  • 第 19 行:用于生成请求响应的 `execute` 方法。
  • 第 21 行:我们检索该会话中已执行的模拟列表
  • 第 22-26 行:若无法从会话中获取此列表,通常意味着会话已过期。我们之前曾遇到过这种情况。此时返回 error 键,该键将显示 Tiles 错误视图:

<!-- action RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      <result name="erreur" type="tiles">erreur</result>
      ...
</action>

第 19.9 节介绍了 Tiles 错误视图。

  • 第 28–36 行:我们验证第 9 行中的 id 字符串确实是一个大于 0 的整数。
  • 第 38–40 行:如果不是这种情况,则再次返回 error 键,这将显示 Tiles 错误视图
  • 第 43–48 行:在模拟列表中搜索要删除的模拟。如果找到,则将其删除。
  • 第 50 行:更新“Tiles 模拟”视图的菜单。
  • 第 51 行:返回 `simulations` 键。这将显示“Tiles 模拟”视图:

<!-- action RetirerSimulation -->
    <action name="SupprimerSimulation" class="web.actions.Supprimer">
      ...
      <result name="simulations" type="tiles">simulations</result>
</action>

“模拟”磁贴视图已在第 19.9 节中介绍。

19.11. 返回表单

“模拟磁贴”视图中,用户可以返回表单:

  • 在 [1] 中,点击链接返回表单
  • 在 [2] 中,会出现一个空表单

[返回模拟表单] 链接在 [Entete.jsp] 片段中定义:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.retourFormulaire">
        |<a href="<s:url action="RetourFormulaire"/>"><s:text name="Menu.RetourFormulaire"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>
  • 第 10 行:该链接指向 [ReturnForm] 操作。该操作在 [struts.xml] 文件中定义如下:

   <!-- action RetourFormulaire -->
    <action name="RetourFormulaire" >
      <result type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
</action>

我们可以看到,此操作未与任何类相关联。它只是将客户端浏览器重定向到 [/Form!input] 操作。因此,我们处于与第 19.7 节中所述显示初始视图时相同的情况。于是,我们返回此初始视图 [2]。

19.12. 查看模拟列表

用户可以在“仿真磁贴”或“输入”视图中请求查看仿真结果:

  • 在 [1] 中,点击 [查看模拟] 链接
  • 在 [2] 中,将显示模拟列表

[查看模拟]链接在[Entete.jsp]片段中定义:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.voirSimulations">
        |<a href="<s:url action="VoirSimulations"/>"><s:text name="Menu.VoirSimulations"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>
  • 第 10 行:链接 [View Simulations] 调用了操作 [ViewSimulations]。该操作在 [struts.xml] 文件中定义如下:

   <!-- action VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
</action>

[ViewSimulations] 操作与 [View] 类相关联,但未指定具体方法。因此,将执行 [View].execute 方法。 [View] 类的定义如下:


package web.actions;
 
import com.opensymphony.xwork2.ActionSupport;
import web.entities.Menu;
 
public class Voir extends ActionSupport{
  // menu
  private Menu menu=new Menu(false,false,false,false,true,true);
  // getters and setters
 
  public Menu getMenu() {
    return menu;
  }
 
  public void setMenu(Menu menu) {
    this.menu = menu;
  }
 
}

[View] 操作只做一件事:为 Tiles 模拟视图设置菜单(第 8 行)。它没有 execute 方法。因此,将执行父类 [ActionSupport] 中的该方法。我们知道该方法除了返回成功标志外,不会执行任何操作。

回到 [struts.xml] 中的该操作:


   <!-- action VoirSimulations -->
    <action name="VoirSimulations" class="web.actions.Voir">
      <result name="success" type="tiles">simulations</result>
</action>

在第 3 行,我们可以看到“success”键会触发 Tiles 模拟视图的显示。这一点已在第 157 页中进行过说明。

19.13. 清除当前模拟

在“Tiles”模拟视图中,用户可以请求清除当前模拟:

  • 在 [1] 中,当前模拟被清除
  • 在 [2] 中,输入表单被清空

[清除模拟]链接在[Entete.jsp]片段中定义:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.effacerSimulation">
        |<a href="<s:url action="Formulaire!input"/>"><s:text name="Menu.EffacerSimulation"/></a><br/>
      </s:if>
      ...
    </td>
  </tr>
</table>

在第 10 行,我们可以看到 [清除模拟] 链接触发了 [Form!input] 操作。我们知道,此操作会导致显示初始视图 [2]。

19.14. 结束当前会话

在任何 Tiles 视图中,用户均可请求结束会话:

  • 在 [1] 中,我们从模拟视图开始并结束会话
  • 在[2]中,输入表单为空。我们请求查看模拟结果。
  • 在[3]中,模拟列表现在为空。

[结束会话]链接在[Entete.jsp]片段中定义:


<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
<table>
  <tr>
    <td><h1><s:text name="Pam.titre"/></h1></td>
    <td>
      ...
      <s:if test="menu.terminerSession">
        |<a href="<s:url action="TerminerSession"/>"><s:text name="Menu.TerminerSession"/></a><br/>
      </s:if>
    </td>
  </tr>
</table>

在第 10 行,我们可以看到 [End Session] 链接触发了 [EndSession] 操作。该操作在 [struts.xml] 文件中的定义如下:


    <action name="TerminerSession" class="web.actions.Terminer">
      <result name="success" type="redirectAction">
        <param name="actionName">Formulaire!input</param>
        <param name="namespace">/</param>
      </result>
</action>
  • 第 1 行:我们可以看到 [Terminer] 类将被实例化,并调用其 execute 方法。
  • 第 2–5 行:在 [Terminer].execute 方法执行后,用户将被重定向到初始输入视图。这解释了屏幕 2。

[Terminer] 类定义如下:


package web.actions;
 
import com.opensymphony.xwork2.ActionSupport;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
 
public class Terminer extends ActionSupport implements SessionAware {
 
  // session
  private Map<String, Object> session;
 
  @Override
  public String execute() {
    // quit current session
    session.clear();
    return SUCCESS;
  }
 
  @Override
  public void setSession(Map<String, Object> session) {
    this.session = session;
  }
}

[Finish] 操作的作用是清除当前会话的属性。

  • 第 7 行:[Terminate] 操作实现了 [SessionAware] 接口以访问会话。
  • 第 10 行:会话字典
  • 第 13 行:调用了 execute 方法
  • 第 15 行:它清空了会话字典。因此,当前会话中的模拟列表将消失。这解释了屏幕 #3 的情况。
  • 第 16 行:返回键值 "success",正如我们所见,这将显示 Tiles 视图并显示值为 [2]。

19.15. 结论

我们已对案例研究第 1 版进行了全面注释,该版本基于模拟的 [业务] 层运行:

剩下的工作就是将真实的业务层与[Web]层“连接”起来。