19. Приклад із практики — версія 1
19.1. Модельований рівень [metier]
Повернемося до архітектури додатка, який ми створюємо:
![]() |
Ми маємо архів шарів [metier, dao, jpa] і вже розглянули елементи цих шарів, про які повинен знати шар [web]. Ми готові написати цей шар за допомогою фреймворку Struts.
Щоб спростити тестування нашого додатка, що перебуває на стадії розробки, ми створимо імітований бізнес-шар, який відповідатиме інтерфейсу шару [metier]. Архітектура матиме такий вигляд:
![]() |
Ми будемо розробляти рівень [web] разом із імітованим рівнем [métier]. Тестування стане простішим, оскільки в архітектурі більше немає бази даних. Завдяки Spring та використанню інтерфейсів подальша заміна імітованого шару [metier] на справжню архітектуру [metier, dao, jpa] не матиме жодного впливу на код шару [web / struts2]. Рівень [web / struts2], який ми зараз розробимо, можна буде використовувати без змін.
Модельований рівень [metier], який ми будемо використовувати, має такий вигляд:
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 {
// список співробітників
private Map<String, Employe> hashEmployes = new HashMap<String, Employe>();
private List<Employe> listEmployes;
// отримати платіжну відомість
public FeuilleSalaire calculerFeuilleSalaire(String SS,
double nbHeuresTravaillées, int nbJoursTravaillés) {
// отримання даних про співробітника за номером SS
Employe e = hashEmployes.get(SS);
// видача фіктивної платіжної відомості
return new FeuilleSalaire(e, new Cotisation(3.49, 6.15, 9.39, 7.88), new ElementsSalaire(100, 100, 100, 100, 100));
}
// список співробітників
public List<Employe> findAllEmployes() {
if (listEmployes == null) {
// створення списку з двох працівників
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)));
// словник співробітників
for (Employe e : listEmployes) {
hashEmployes.put(e.getSS(), e);
}
}
// виводиться список співробітників
return listEmployes;
}
}
- рядок 11: клас [MetierSimule] реалізує інтерфейс [IMetier], який реалізує справжній шар [metier].
- рядок 14: словник співробітників, індексований за їхнім номером INSEE
- рядок 15: список співробітників
- рядки 27–39: реалізація методу findAllEmployes інтерфейсу [IMetier].
- рядки 30–33: створення списку з двох співробітників
- рядки 34–36: створення словника співробітників, індексованого за номером INSEE
- рядки 18–24: реалізація методу calculerSalaire інтерфейсу [IMetier]. Тут повертається фіктивна платіжна відомість.
19.2. Проєкт NetBeans
Проєкт NetBeans має такий вигляд:
![]() |
- у [1]:
- [applicationContext.xml] — це файл конфігурації Spring
- [tiles.xml] — це файл конфігурації фреймворку під назвою Tiles.
- [web.xml] — це файл конфігурації веб-додатку
- у [2]: різні види додатка
- у [3]:
- [messages.properties] — файл повідомлень
- [struts.xml]: файл конфігурації Struts
![]() |
- у [4]: вихідні коди додатка. Дії Struts знаходяться у пакеті [web.actions].
- у [5]: імітований рівень [metier]
- у [6]: використовувані архіви. Тут містяться архіви різних використовуваних інструментів: Spring, Tiles, Struts 2, плагін інтеграції Struts 2 / Spring, плагін інтеграції Struts 2 / Tiles.
- у [7]: архів справжнього шару [metier, dao, jpa]. Він надає нам доступ до сутностей JPA, до інтерфейсу [IMetier], до класів [FeuilleSalaire] та [ElementsSalaire]. Усі ці елементи фактично використовуються нашим класом [MetierSimule].
19.3. Налаштування проєкту
Проєкт налаштовується за допомогою різних файлів:
- [web.xml], який налаштовує веб-додаток
- [struts.xml], що налаштовує фреймворк Struts
- [applicationContext.xml], що налаштовує фреймворк Spring
- [tiles.xml], що налаштовує фреймворк Tiles
19.3.1. Налаштування веб-додатку
Файл [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>
<!-- Плитки -->
<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. Клас [org.apache.struts2.tiles.StrutsTilesListener] буде інстанційовано під час запуску веб-додатку. Після цього він почне використовувати свій файл конфігурації. Цей файл визначено у рядках 5–8. Отже, файл конфігурації Tiles — це файл [WEB-INF/tiles.xml].
У підсумку під час запуску додатка Struts створюються екземпляри трьох класів:
- один — для фільтра Struts 2. Саме він відповідає за «C» у MVC.
- інший — для прослуховувача Spring. Spring використовуватиме файл [applicationContext.xml] для створення екземплярів шарів [métier, dao, jpa] додатка. Spring також створить, як і в попередньому прикладі, екземпляр класу [Config], який міститиме дані області дії Application. Нарешті, Spring введе в кожну дію Struts, яка цього потребує, посилання на цей єдиний екземпляр [Config].
- Ще один екземпляр призначений для слухача 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>
<!-- інтернаціоналізація -->
<constant name="struts.custom.i18n.resources" value="messages" />
<!-- інтеграція Spring -->
<constant name="struts.objectFactory.spring.autoWire" value="name" />
<!-- дії Struts /Tiles -->
<package name="default" namespace="/" extends="tiles-default">
<!-- акція за замовчуванням -->
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
<!-- акція «Форма» -->
<action name="Formulaire" class="web.actions.Formulaire" method="input">
<result name="success" type="tiles">saisie</result>
<result name="exception" type="tiles">exception</result>
</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>
<!-- дія 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>
<!-- дія RetourFormulaire -->
<action name="RetourFormulaire" >
<result type="redirectAction">
<param name="actionName">Formulaire</param>
<param name="namespace">/</param>
</result>
</action>
<!-- акція VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</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>
<!-- акція 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 у дії Struts. Ін'єкція здійснюється за іменем біна. Поле дії Struts, яке має бути ініціалізоване Spring, повинно мати ім'я біна, що підлягає ін'єкції.
- рядок 25: визначає вигляд, який слід відобразити для ключа навігації success дії [Formulaire]. Бачимо, що результат <result> має атрибут type='tiles', про який ми не знаємо. Нам був відомий тип redirect, який дозволяє перенаправити клієнта на представлення. Тут представлення типу tiles управляється фреймворком Tiles. Тип tiles визначено у файлі [struts-plugin.xml] з архіву [struts2-tiles-plugin-2.2.3.1.jar]:
- рядки 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">
<!-- прикладні шари -->
<!-- веб -->
<bean id="config" class="web.Config" init-method="init">
<property name="metier" ref="metier"/>
</bean>
<!-- бізнес-логіка -->
<bean id="metier" class="metier.MetierSimule"/>
</beans>
- рядок 13: імітований шар [metier], інстанційований класом [metier.MetierSimule]
- рядки 9–11: визначають бін із назвою config. Як і в раніше розглянутому прикладі, цей бін слугуватиме для інкапсуляції інформації з області дії Application. Клас, пов’язаний із цим біном, — це наступний клас [Config]:
package web;
import java.util.List;
import jpa.Employe;
import metier.IMetier;
public class Config {
// бізнес-шар, ініціалізований Spring
private IMetier metier;
// список співробітників
private List<Employe> employes;
// помилки
private Exception initException;
// конструктор
public Config() {
}
// метод Spring для ініціалізації об’єкта
public void init() {
// запит списку співробітників
try {
employes = metier.findAllEmployes();
} catch (Exception ex) {
initException = ex;
}
}
// гетери та сеттери
...
}
Повернемося до конфігурації біна config:
<bean id="config" class="web.Config" init-method="init">
<property name="metier" ref="metier"/>
</bean>
<!-- бізнес-логіка -->
<bean id="metier" class="metier.Metier">
...
</bean>
У рядку 2 видно, що bean metier із рядка 5 вводиться (ref) у поле з іменем metier (name) об’єкта [Config]. Bean metier є посиланням на рівні [metier]:
![]() |
Для взаємодії з рівнем [metier] усі дії Struts на рівні [web] потребуватимуть посилання на цей рівень. Можна сказати, що посилання на шар [metier] є даними області дії Application. Воно буде потрібне для всіх запитів усіх користувачів. Тому ми розміщуємо це посилання в об’єкті [Config]. Крім того, у рядку 1 конфігурація біна config має атрибут init-method. Цей атрибут вказує на метод біна, який слід виконати після його інстанціювання. Тут вказано, що після інстанціювання класу [web.Config] слід виконати його метод init. Він має такий вигляд:
// бізнес-шар, ініціалізований Spring
private IMetier metier;
// список співробітників
private List<Employe> employes;
// помилки
private Exception initException;
// конструктор
public Config() {
}
// метод Spring для ініціалізації об’єкта
public void init() {
// запит списку співробітників
try {
employes = metier.findAllEmployes();
} catch (Exception ex) {
initException = ex;
}
}
Під час виконання методу init поле metier класу було інстанційовано Spring. Отже, метод init має доступ до бізнес-шару інтерфейсу [IMetier] (рядок 2):
package metier;
import java.util.List;
import jpa.Employe;
public interface IMetier {
// отримати платіжну відомість
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// список співробітників
List<Employe> findAllEmployes();
}
- рядок 8: метод дозволяє обчислити заробітну плату співробітника
- рядок 10: метод дозволяє отримати список співробітників
Бачимо, що метод init запитує список співробітників у шарі [métier]. Цей список зберігається у полі рядка 4. Якщо виникає виняток, він записується у поле рядка 6.
Підсумовуючи, єдиний об’єкт [Config] містить:
- посилання на рівень [métier]
- список співробітників
19.4. Генерація Tiles-видів
Як ми бачили у файлі конфігурації Struts, види будуть генеруватися фреймворком Tiles. Ми пояснимо лише те, що є строго необхідним для написання нашої програми.
Tiles дозволяє генерувати подання на основі шаблону. Цей шаблон, який тут називається [MasterPage.JSP], буде складатися з таких фрагментів JSP:
Ці фрагменти JSP визначені в проєкті NetBeans:
![]() |
Фреймворк 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>
<!-- головна сторінка -->
<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>
<!-- погляд «Введення даних» -->
<definition name="saisie" extends="masterPage">
<put-attribute name="saisie" value="/Saisie.JSP"/>
</definition>
<!-- вікно «Моделювання» -->
<definition name="simulation" extends="saisie">
<put-attribute name="simulation" value="/Simulation.JSP"/>
</definition>
<!-- перегляд «Моделювання» -->
<definition name="simulations" extends="masterPage">
<put-attribute name="simulations" value="/Simulations.JSP"/>
</definition>
<!-- вигляд «Виняток» -->
<definition name="exception" extends="masterPage">
<put-attribute name="exception" value="/Exception.JSP"/>
</definition>
<!-- вигляд «помилка» -->
<definition name="erreur" extends="masterPage">
<put-attribute name="erreur" value="/Erreur.JSP"/>
</definition>
</tiles-definitions>
- зазначений вище файл визначає шість подань Tiles з такими назвами: masterPage (рядок 9), введення (рядок 20), моделювання (рядок 25), моделювання (рядок 30), виняток (рядок 35), помилка (рядок 40).
- рядки 9–17: визначають вигляд із назвою masterPage (name), пов’язаний із шаблоном [MasterPage.JSP] (template). Ми бачили, що ця сторінка JSP визначає шість підвидів. Вид Tiles, пов’язаний із головною сторінкою, повинен вказувати фрагмент JSP, пов’язаний із кожним із шести підвидів. Бачимо, що деякі підвиди отримують як значення (value) порожній рядок. Ці підвиди не будуть включені до головної сторінки [MasterPage.JSP]. Отже, вигляд Tiles, названий masterPage, складається лише з одного підфрагмента [Entete.JSP].
- рядки 20–22: визначають вигляд під назвою saisie (name), який розширює (extends) вигляд під назвою 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. Отже, підвигляд Tiles, названий saisie, складається з фрагментів JSP та [Entete.JSP, Saisie.JSP]
Якщо продовжити це міркування, отримаємо таку таблицю:
погляд Tiles | сторінки 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
# формати
Format.heure = {0,time}
Format.nombre = {0,number,#0.0##}
Format.pourcent = {0,number,##0.00' %'}
Format.monnaie={0,number,##0.00' \u20ac'}
# список симуляцій
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.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
# повідомлення про помилку
Erreur.sessionexpiree=La session a expir\u00e9
Erreur.numSimulation=N\u00b0 de simulation incorrect
# помилка конвертації
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".
Файл [Formulaire.properties] має такий вигляд:
# щоб дублікати мали місцевий формат
double.format={0,number,#0.00##}
# повідомлення про помилку
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] ми маємо такі дії:
<!-- дія за замовчуванням -->
<default-action-ref name="index" />
<action name="index">
<result type="redirectAction">
<param name="actionName">Formulaire!input</param>
<param name="namespace">/</param>
</result>
</action>
<!-- дія «Форма» -->
<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: дією за замовчуванням для додатка є [Formulaire!input].
Клас [Formulaire] має такий вигляд:
package web.actions;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
// конфігурація, ініціалізована Spring
private Config config;
// список співробітників
private List<Employe> employes;
// список помилок
private List<Erreur> erreurs;
// розрахунковий лист
private FeuilleSalaire feuilleSalaire;
// введені дані
private String comboEmployesValue;
private Double heuresTravaillees;
private Integer joursTravailles;
// сесія
private Map<String, Object> session;
// меню
private Menu menu;
@Override
public void prepare() throws Exception {
...
}
@Override
public String input() {
....
}
// розрахунок заробітної плати
public String calculSalaire() {
...
}
}
@Override
public void validate() {
...
}
@Override
public void setSession(Map<String, Object> map) {
session = map;
}
// гетери та сеттери
...
}
- рядок 4: дія [Formulaire] реалізує інтерфейс Preparable. Цей інтерфейс має лише один метод — метод prepare у рядку 24. Цей метод виконується один раз перед виконанням будь-якого методу дії. Зазвичай він використовується для ініціалізації моделі дії.
Модель дії складається з рядків 6–21:
- рядок 7: поле config ініціалізується Spring, як було пояснено раніше. Воно надає доступ до даних області дії:
- посилання на рівень [métier]
- посилання на список співробітників
- посилання на виняток, який міг виникнути під час інстанціювання об’єкта [Config]
- рядок 9: список співробітників. Він буде заповнювати комбінований список співробітників у фрагменті [Saisie.JSP].
- рядок 11: список помилок. Він буде заповнювати фрагмент [Erreur.JSP].
- рядок 21: список опцій меню фрагмента [Entete.JSP]
![]() |
У [1] посилання у відображеному меню керуються полем menu дії [Formulaire].
Метод prepare виконується перед методом input. Він має такий вигляд:
@Override
public void prepare() throws Exception {
// помилка конфігурації?
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: виняток зберігається в об’єкті [Config], інстанційованому Spring
- рядок 5: якщо під час інстанціювання об’єкта [Config] сталося виключення, то ініціалізується список помилок у рядку 11. Клас [Erreur] має такий вигляд:
package web.entities;
import java.io.Serializable;
public class Erreur implements Serializable{
public Erreur() {
}
// поля
private String classe;
private String message;
// конструктор
public Erreur(String classe, String message){
this.setClasse(classe);
this.message=message;
}
// геттери та сеттери
...
}
Цей клас слугує для зберігання стека винятків:
- рядок 11: клас винятку
- рядок 12: повідомлення про виняток
Повернемося до методу prepare:
- рядок 13: список співробітників об’єкта [Config] зберігається у полі employes дії.
Після виконання методу prepare буде виконано метод input. Він виглядає так:
@Override
public String input() {
if (erreurs == null) {
// меню
menu = new Menu(true, false, false, true, false, true);
return SUCCESS;
} else {
// меню
menu = new Menu(false, false, false, false, false, false);
return "exception";
}
}
Метод input лише визначає список опцій меню, які потрібно відобразити. Клас [Menu] має такий вигляд:
package web.entities;
import java.io.Serializable;
public class Menu implements Serializable {
// елементи меню
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;
}
// методи getter та setter
...
}
- рядки 8–13: у меню може бути 6 посилань
- рядки 18–25: конструктор класу дозволяє визначити, які посилання мають відображатися, а які — ні.
Посилання меню відображаються у [Entete.JSP], фрагмент JSP присутній у всіх поданнях Tiles. Кожна дія матиме поле menu для керування відображенням меню [Entete.JSP].
Повернемося до методу input:
@Override
public String input() {
if (erreurs == null) {
// меню
menu = new Menu(true, false, false, true, false, true);
return SUCCESS;
} else {
// меню
menu = new Menu(false, false, false, false, false, false);
return "exception";
}
}
- рядки 3–6: якщо список помилок порожній, буде відображено меню [Faire la simulation, Voir les simulations, Terminer la session] і повернуто ключ input.
- рядки 9–10: якщо список помилок не порожній, меню буде порожнім, а ключ exception буде повернуто.
Повернемося до налаштування дії [Formulaire] у [struts.xml]:
<!-- дія «Форма» -->
<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 відображає вигляд Tiles із назвою saisie
- рядок 4: ключ exception відображає вигляд Tiles з назвою exception
Почнемо з подання «Tiles» з назвою saisie. Воно складається з фрагментів JSP, [Entete.JSP] та [Saisie.JSP].
Фрагмент [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.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: відображення посилань керується полем menu поточної дії.
Слід зауважити, що фрагмент [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-й рядок. Це наступний вигляд:
![]() |
Код фрагмента [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(){
// відправлення форми
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- введення інформації -->
<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='Formulaire'.
- рядок 18: відображення комбінованого списку співробітників. Вміст комбінованого списку (атрибут list) надається полем employes поточної дії. Атрибут value опцій відповідатиме номеру SS співробітників (атрибут listKey). Текст, що відображається для кожної опції, — це ім’я та прізвище працівника (атрибут listValue). Номер SS працівника, обраного у списку, буде занесено до поля [Formulaire].comboEmployesvalue (атрибут name).
- рядок 19: поле для введення відпрацьованих годин. Значення, що відображається (атрибут value), відповідає значенню поля heuresTravaillees дії [Formulaire] у такому форматі (Formulaire.properties):
double.format={0,number,#0.00##}
Значення буде занесено до поля [Formulaire].heuresTravaillees (атрибут name).
- рядок 20: поле введення відпрацьованих днів. Відображуване значення (атрибут value) відповідає значенню поля joursTravailles дії [Formulaire].
Значення буде записано в поле [Formulaire].joursTravailles (атрибут name).
У підсумку, вигляд Tiles saisie, що відображається під час запуску за відсутності помилок, є таким:
![]() |
Повернемося до налаштувань дії [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>
Ми бачили, що дія [Formulaire].input також може повернути ключ exception із рядка 4. У цьому випадку відображається панель Tiles під назвою exception. Вона складається з фрагментів [Entete.JSP] та [Exception.JSP]. Ми вже розглядали фрагмент [Entete.JSP]. Фрагмент [Exception.JSP] має такий вигляд:
![]() |
Це стартовий екран версії 2 додатка, коли Sgbd не запущено. Код JSP фрагмента [Erreur.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: ітератор над колекцією List<Erreur> errors дії [Formulaire]. Нагадаємо, що в разі помилки туди зберігався стек винятків.
19.8. Провести симуляцію
Отримавши початковий вигляд, можна розрахувати заробітну плату за посиланням [Faire une simulation].
19.8.1. Перевірка введених даних
Розглянемо таку послідовність:
![]() |
- у [1] — помилкове введення даних
- в [2] — надіслана відповідь.
Розглянемо конфігурацію дії [Formulaire] у [struts.xml]:
<!-- дія з формою -->
<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 saisie. Процес перевірки передбачає, що поля з помилками супроводжуються повідомленнями про помилки.
Валідація дії [Formulaire] забезпечується наступним файлом [Formulaire-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 перевіряють, чи поле heuresTravaillees є дійсним числом у діапазоні [0,300].
- рядки 22–36 перевіряють, чи поле joursTravailles є цілим числом в інтервалі [0,31].
Повернемося до налаштування дії [Formulaire] у [struts.xml]:
<!-- дія «Форма» -->
<action name="Formulaire" class="web.actions.Formulaire">
...
<result name="input" type="tiles">saisie</result>
</action>
Відомо, що в разі помилки валідації перехоплювач валідації повертає ключ input. Отже, повертається саме представлення Tiles saisie.
Нагадаємо, що вона складається з фрагментів [Entete.JSP] та [Saisie.JSP], де [Entete.JSP] містить заголовок та перелік варіантів вибору, а [Saisie.JSP] — форму введення даних. У разі помилок введення даних процес перевірки забезпечує, щоб помилкові поля супроводжувалися повідомленнями про помилки та додатково відображали свої помилкові значення. Фрагмент [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.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>
Шість посилань налаштовуються полем menu шаблону (рядки 8, 11, 14, 17, 20, 23). Коли трапляється помилка, цей шаблон не оновлюється дією, і ми бачимо сторінку без меню. Щоб вирішити цю проблему, клас [Formulaire] має такий метод validate:
package web.actions;
import com.opensymphony.xwork2.ActionSupport;
...
public class Formulaire extends ActionSupport implements Preparable, SessionAware {
...
// меню
private Menu menu;
@Override
public void prepare() throws Exception {
...
}
@Override
public String input() {
...
}
// розрахунок заробітної плати
public String calculSalaire() {
...
}
@Override
public void validate() {
// помилки?
if (!getFieldErrors().isEmpty()) {
// меню
menu = new Menu(true, false, false, true, false, true);
}
}
// гетери та сеттери
...
}
- рядок 27: відомо, що за наявності цього методу метод validate виконується в процесі валідації. Цим користуємося, щоб оновити меню в рядку 4, яке є частиною шаблону фрагмента [Entete.JSP].
- рядки 29–32: якщо трапилися помилки валідації, то налаштовуємо меню для повторного відображення подання Tiles saisie. Якщо помилок валідації не було, то нічого не робимо. Саме метод 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: коли користувач натискає на посилання [Faire la simulation], виконується функція JavaScript doSimulation. Вона визначена у фрагменті [Saisie.JSP]:
<script language="javascript" type="text/javascript">
function doSimulation(){
// відправлення форми
document.forms['Saisie'].elements['action'].name='action:Formulaire!calculSalaire'
document.forms['Saisie'].submit();
}
</script>
<!-- введення інформації -->
<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 буде передано до дії [Formulaire]. Це поле дозволить нам вказати дію та метод, які мають бути виконані у полі POST форми. Можливо, ви пам’ятаєте з перших прикладів, що їх можна вказати в параметрі з іменем action:Action!method. Значення цього параметра не має значення. Достатньо, щоб він був присутній.
- рядки 2–6: функція JavaScript, яка виконується, коли користувач натискає на посилання [Faire la simulation] у фрагменті [Entete.JSP].
- рядок 4: змінюємо атрибут name прихованого поля action. Ми робимо так, щоб він мав форму action:Action!метод, яку очікує Struts.
- рядок 5: форма з назвою Saisie з рядка 5 відправляється. У результаті відправляється такий рядок параметрів:
SS1 : n° INSEE de l'employé sélectionné dans le combo
heuresTravaillees : nombre d'heures travaillées
joursTravailles : nombre de jours travaillés
action : Formulaire!calculSalaire : les éléments ci-dessus seront postés à l'action [Formulaire] puis la méthode calculSalaire de cette action sera exécutée.
Метод [Formulaire].calculSalaire полягає в наступному:
// розрахунок заробітної плати
public String calculSalaire() {
try {
// розрахунок заробітної плати
feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
// додавання симуляції до сесії
session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
// меню
menu = new Menu(true, true, true, true, false, true);
// завершено
return "simulation";
} catch (Throwable th) {
...
}
}
- рядок 5: запит на розрахунок відомості про заробітну плату надсилається до шару [métier]
- рядок 7: до сесії користувача додається об’єкт типу Simulation. Адже він може знадобитися під час подальшого запиту. Клас [Simulation] має такий вигляд:
package web.entities;
import java.io.Serializable;
import metier.FeuilleSalaire;
public class Simulation implements Serializable{
public Simulation() {
}
// поля симуляції
private Integer num;
private FeuilleSalaire feuilleSalaire;
private String heuresTravaillées;
private String joursTravaillés;
// конструктор
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();
}
// методи getter і setter
...
}
- рядок 12: номер моделювання. Збільшується з кожним новим зареєстрованим моделюванням.
- рядок 13: платіжна відомість працівника
- рядок 14: кількість відпрацьованих годин
- рядок 15: кількість відпрацьованих днів
- рядок 25: метод getIndemnites повертає суму виплат працівника
Ми побачимо, що клас [Simulation] є шаблоном фрагмента [Simulations.JSP], який містить усі виконані симуляції.
Повернемося до методу [Formulaire].calculSalaire:
// розрахунок заробітної плати
public String calculSalaire() {
try {
// розрахунок заробітної плати
feuilleSalaire = config.getMetier().calculerFeuilleSalaire(comboEmployesValue, heuresTravaillees, joursTravailles);
// додаємо симуляцію до сесії
session.put("simulation", new Simulation(0, "" + heuresTravaillees, "" + joursTravailles, feuilleSalaire));
// меню
menu = new Menu(true, true, true, true, false, true);
// завершено
return "simulation";
} catch (Throwable th) {
...
}
- рядок 9: оновлення меню
- рядок 11: повернення ключа навігації simulation.
Повернення до налаштувань дії [Formulaire]:
<!-- дія «Форма» -->
<action name="Formulaire" class="web.actions.Formulaire">
...
<result name="simulation" type="tiles">simulation</result>
</action>
У рядку 4 показано, що ключ навігації simulation викликає відображення подання Tiles з назвою simulation. Воно складається з таких фрагментів JSP: [Entete, Saisie, Simulation].
Відображений вигляд має такий вигляд:
![]() |
- у [1] фрагмент [Entete.JSP]
- у [2] — фрагмент [Saisie.JSP]
- у [3], фрагмент [Simulation.JSP]. Нагадаємо, що представлена платіжна відомість є фіктивною платіжною відомістю, згенерованою шаром [metier].
Перші два фрагменти вже були представлені. Фрагмент [Simulation.JSP] має такий вигляд:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<hr/>
<!-- інформація про працівника -->
<span class="titreInfos">
<s:text name="Simulation.Infos.employe"/>
</span>
<br/><br/>
<table>
<!-- рядок 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>
<!-- рядок 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>
<!-- рядок 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>
<!-- рядок 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>
<!-- інформація про внески -->
<br/>
<span class="titreInfos">
<s:text name="Simulation.Infos.cotisations"/>
</span>
<br/><br/>
<table>
<!-- рядок 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>
<!-- рядок 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>
<!-- інформація про виплати -->
<br/>
<span class="titreInfos">
<s:text name="Form.Infos.indemnites"/>
</span>
<br/><br/>
<table>
<!-- рядок 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>
<!-- рядок 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>
<!-- інформація про заробітну плату -->
<br/>
<span class="titreInfos">
<s:text name="Simulation.Infos.Salaire"/>
</span>
<br/><br/>
<table>
<!-- рядок 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>
<!-- рядок 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>
<!-- Чиста заробітна плата-->
<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>
Це довго... але функціонально просто. Цей фрагмент відображає різні властивості поля [Formulaire].feuilleSalaire, яке представляє платіжну відомість працівника.
Повернемося до методу [Formulaire].calculSalaire:
// розрахунок заробітної плати
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 = new Menu(false, false, false, false, true, true);
return "exception";
}
}
Розрахунок заробітної плати може пройти з помилками. Це може статися, зокрема, якщо буде порушено зв’язок із СГБД. У цьому випадку обробляється виняток, що виник. Ми вже стикалися з таким випадком під час вивчення методу [Formulaire].input.
- рядки 7–11: створюємо список об’єктів Erreur на основі стека винятків
- рядок 13: встановлюємо меню
- рядок 14: повертається ключ exception.
Ключ exception відобразить вигляд Tiles exception:
<!-- дія «Форма» -->
<action name="Formulaire" class="web.actions.Formulaire">
<result name="exception" type="tiles">exception</result>
...
</action>
Цей вигляд Tiles вже було представлено. Він має такий вигляд:
![]() |
19.9. Збереження симуляції
Після виконання симуляції користувач може захотіти зберегти її в сесії.
![]() |
![]() |
- у [1] зберігається симуляція
- у [2] — відповідь, що містить список вже виконаних симуляцій, до якого додано нову симуляцію
Посилання [Enregistrer la simulation] міститься у фрагменті [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>
Бачимо, що клік на посилання викликає виконання дії [EnregistrerSimulation]. Вона налаштована у файлі [struts.xml] наступним чином:
<!-- дія 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: дія [EnregistrerSimulation] пов’язана з класом [Enregistrer] та його методом execute.
Клас [Enregistrer] має такий вигляд:
package web.actions;
...
public class Enregistrer extends ActionSupport implements SessionAware {
// сесія
private Map<String, Object> session;
// меню
private Menu menu;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
// виконання дії
public String execute() {
// отримання останнього моделювання в сесії
Simulation simulation = (Simulation) session.get("simulation");
if (simulation == null) {
return ERROR;
}
...
}
// гетери та сеттери
...
}
- рядок 4: оскільки дія повинна мати доступ до сесії, вона реалізує інтерфейс SessionAware.
- рядок 7: сесія
- рядок 9: меню
Коли дія [Enregistrer] була інстанційована, виконується її метод execute. Нагадаємо, що його завданням є додавання останньої симуляції до сесії. Ця симуляція буде додана до списку вже виконаних симуляцій, який також зберігається в сесії.
- рядок 19: з сесії витягується останнє моделювання, яке було туди поміщене.
- рядки 20–22: якщо її не знайдено, то, ймовірно, сесія закінчилася. Адже вона триває лише певний час, який можна встановити у файлі [web.xml], що налаштовує додаток.
- рядок 21: повертаємо ключ error.
Повернення до налаштувань дії [EnregistrerSimulation]:
<!-- дія 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) викликає відображення подання « Tiles» з назвою erreur. Вона складається з фрагментів [Entete.JSP] та [Erreur.JSP] і має такий вигляд:
![]() |
Фрагмент [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>
Повернення до методу [Enregistrer].execute:
// виконання дії
public String execute() {
// отримання останнього моделювання в сесії
Simulation simulation = (Simulation) session.get("simulation");
if (simulation == null) {
return ERROR;
}
// отримано номер останнього моделювання
Integer numDerniereSimulation = (Integer) session.get("numDerniereSimulation");
if (numDerniereSimulation == null) {
numDerniereSimulation = 0;
}
// збільшуємо його
numDerniereSimulation++;
// заносимо новий номер у сесію
session.put("numDerniereSimulation", numDerniereSimulation);
// отримуємо список симуляцій
List<Simulation> simulations = (List<Simulation>) session.get("simulations");
if (simulations == null) {
simulations = new ArrayList<Simulation>();
session.put("simulations", simulations);
}
// додаємо до нього поточне моделювання
simulation.setNum(numDerniereSimulation);
simulations.add(simulation);
// виводиться список симуляцій
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.
Повернення до налаштувань дії [EnregistrerSimulation] у [struts.xml]:
<!-- дія 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 викликає відображення подання Tiles з назвою simulations. Це подання складається з фрагментів [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" %>
<!-- порожній список симуляцій -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
<h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- непорожній список симуляцій -->
<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"/>">Видалити</a></td>
</tr>
</s:iterator>
</table>
</s:if>
- рядки 5–7: якщо в сесії немає симуляцій, то відображається такий вигляд:
![]() |
- рядки 13–21: відображення заголовків стовпців таблиці
- рядки 23–55: цикл по списку симуляцій, знайдених у сесії
- рядки 24–26: створення об’єкта URL з іменем URL (атрибут id). Посилання на HTML-код, згенероване цим об’єктом URL, має такий вигляд:
<a href="<a href="view-source:http://localhost:8084/pam/SupprimerSimulation.action?id=1">/pam/SupprimerSimulation.action?id=1</a>">Retirer</a>
Бачимо, що посилання вказує на дію [SupprimerSimulation] із параметром id, який представляє номер симуляції, яку потрібно видалити зі списку.
- рядки 28–54: під час кожної ітерації по списку симуляцій відображаються властивості поточної симуляції.
![]() |
19.10. Видалення симуляції
Користувач може захотіти видалити симуляцію зі списку симуляцій:
![]() |
![]() |
- у [1] видаляється симуляція № 1
- у [2] симуляція № 1 була видалена
Посилання [Retirer] міститься у фрагменті [Simulations.JSP], який ми вже розглядали:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!-- порожній список симуляцій -->
<s:if test="#session['simulations']==null || #session['simulations'].size()==0">
<h2><s:text name="Pam.SimulationsVides.titre"/></h2>
</s:if>
<!-- непорожній список симуляцій -->
<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"/>">Видалити</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 — номер симуляції, яку потрібно видалити.
Дія [SupprimerSimulation] визначена у файлі [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>
<!-- інтернаціоналізація -->
<constant name="struts.custom.i18n.resources" value="messages" />
<!-- інтеграція Spring -->
<constant name="struts.objectFactory.spring.autoWire" value="name" />
<!-- дії Struts /Tiles -->
<package name="default" namespace="/" extends="tiles-default">
...
<!-- дія RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
<result name="erreur" type="tiles">erreur</result>
<result name="simulations" type="tiles">simulations</result>
</action>
...
</package>
<!-- Додати пакети тут -->
</struts>
- рядок 16: дія [SupprimerSimulation] пов’язана з класом [Supprimer]. Оскільки метод не вказано, буде виконано її метод execute. Клас [Supprimer] має такий вигляд:
package web.actions;
...
public class Supprimer extends ActionSupport implements SessionAware {
// сесія
private Map<String, Object> session;
// ідентифікатор симуляції, яку потрібно видалити
private String id;
// меню
private Menu menu;
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
// виконання дії
public String execute() {
// отримання симуляцій із сесії
List<Simulation> simulations = (List<Simulation>) session.get("simulations");
if (simulations == null) {
// аномальна ситуація — термін дії сесії, ймовірно, закінчився
menu = new Menu(false, false, false, false, true, true);
return "erreur";
}
// перевірка ідентифікатора
int num = 0;
boolean erreur = false;
try {
num = Integer.parseInt(id);
erreur = num <= 0;
} catch (NumberFormatException ex) {
// аномалія
erreur = true;
}
// помилка?
if (erreur) {
menu = new Menu(false, false, false, false, true, true);
return "erreur";
}
// шукається симуляція для видалення
for (int i = 0; i < simulations.size(); i++) {
if (num == simulations.get(i).getNum()) {
simulations.remove(i);
break;
}
}
// відображається список симуляцій
menu = new Menu(false, false, false, false, true, true);
return "simulations";
}
// гетери та сеттери
...
}
- рядок 4: дія [Supprimer] реалізує інтерфейс [SessionAware] для отримання доступу до сесії.
- рядок 7: сесія
- рядок 9: номер симуляції, яку потрібно видалити. Слід пам’ятати, що інстанціювання класу [Supprimer] здійснюється через URL 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 — номер симуляції, яку потрібно видалити. Цей номер буде збережено в полі id у рядку 9.
- рядок 11: меню для перегляду, яке буде відображено у відповідь на запит
- рядок 19: метод execute, який згенерує відповідь на запит.
- рядок 21: отримуємо список симуляцій, вже виконаних у сесії
- рядки 22–26: відсутність цього списку в сесії, ймовірно, означає, що термін дії сесії закінчився. Ми вже стикалися з таким випадком. Повертаємо ключ erreur, який відображає вигляд Tiles erreur:
<!-- дія RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
<result name="erreur" type="tiles">erreur</result>
...
</action>
Вигляд Tiles erreur було представлено в параграфі 19.9.
- рядки 28–36: перевіряємо, чи рядок id у рядку 9 дійсно є цілим числом > 0.
- рядки 38–40: якщо це не так, знову повертається ключ erreur, який відобразить вигляд Tiles erreur
- рядки 43–48: у списку симуляцій шукається симуляція, яку потрібно видалити. Якщо її знайдено, вона видаляється.
- рядок 50: оновлюється меню для виду Tiles simulations.
- рядок 51: повертається ключ simulations. Він забезпечить відображення виду Tiles simulations:
<!-- дія RetirerSimulation -->
<action name="SupprimerSimulation" class="web.actions.Supprimer">
...
<result name="simulations" type="tiles">simulations</result>
</action>
Вигляд Tiles simulations було розглянуто в розділі 19.9.
19.11. Повернутися до форми
З перегляду «Tiles» simulations користувач може повернутися до форми:
![]() |
![]() |
- у [1] слід натиснути на посилання для повернення до форми
- у [2] з’являється порожня форма
Посилання [Retour au formulaire de simulation] визначено у фрагменті [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: посилання вказує на дію [RetourFormulaire]. Вона визначена наступним чином у файлі [struts.xml]:
<!-- дія RetourFormulaire -->
<action name="RetourFormulaire" >
<result type="redirectAction">
<param name="actionName">Formulaire!input</param>
<param name="namespace">/</param>
</result>
</action>
Бачимо, що ця дія не пов’язана з жодним класом. Вона просто перенаправляє браузер клієнта на дію [/Formulaire!input]. Отже, ми маємо ту саму ситуацію, що й під час відображення початкового виду, описаного в параграфі 19.7. Тому ми знову бачимо цей початковий вид [2].
19.12. Переглянути список симуляцій
З переглядів Tiles simulation або saisie користувач може переглянути симуляції:
![]() |
![]() |
- у [1] слід натиснути на посилання [Voir les simulations]
- у [2] можна переглянути список симуляцій
Посилання [Voir les simulations] визначено у фрагменті [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: посилання [Voir les simulations] викликає дію [VoirSimulations]. Вона визначена наступним чином у файлі [struts.xml]:
<!-- дія VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
Дія [VoirSimulations] пов’язана з класом [Voir] без вказівки методу. Отже, буде виконано метод [Voir].execute. Клас [Voir] має такий вигляд:
package web.actions;
import com.opensymphony.xwork2.ActionSupport;
import web.entities.Menu;
public class Voir extends ActionSupport{
// меню
private Menu menu=new Menu(false,false,false,false,true,true);
// методи getter та setter
public Menu getMenu() {
return menu;
}
public void setMenu(Menu menu) {
this.menu = menu;
}
}
Дія [Voir] виконує лише одну операцію: встановлює меню для режиму перегляду «Tiles simulations» (рядок 8). Методу execute не існує. Отже, буде виконано метод батьківського класу [ActionSupport]. Відомо, що вона нічого не робить, окрім як повертає ключ success.
Повернемося до коду в [struts.xml]:
<!-- дія VoirSimulations -->
<action name="VoirSimulations" class="web.actions.Voir">
<result name="success" type="tiles">simulations</result>
</action>
У рядку 3 бачимо, що ключ success призводить до відображення подання Tiles simulations. Воно було описано на сторінці 161.
19.13. Скидання поточної симуляції
З екрану «Tiles» simulation користувач може подати запит на видалення поточної симуляції:
![]() |
![]() |
- у [1] видаляється поточна симуляція
- у [2] форму введення даних знову відображається порожньою
Посилання [Effacer la simulation] визначено у фрагменті [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 видно, що посилання [Effacer la simulation] запускає дію [Formulaire!input]. Відомо, що ця дія переводить до початкового екрану [2].
19.14. Завершити поточну сесію
З будь-якого екрану Tiles користувач може подати запит на завершення сеансу:
![]() |
![]() |
![]() |
- у [1] переходимо з екрану симуляцій і завершуємо сесію
- у [2] з’являється порожня форма введення даних. Запитуємо перегляд симуляцій.
- у [3] список симуляцій тепер порожній.
Посилання [Terminer la session] визначено у фрагменті [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 видно, що посилання [Terminer la session] запускає дію [TerminerSession]. Вона визначена наступним чином у файлі [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 {
// сесія
private Map<String, Object> session;
@Override
public String execute() {
// припинення поточної сесії
session.clear();
return SUCCESS;
}
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
}
Роль дії [Terminer] полягає у очищенні поточної сесії від її атрибутів.
- рядок 7: дія [Terminer] реалізує інтерфейс [SessionAware] для отримання доступу до сесії.
- рядок 10: словник сесії
- рядок 13: метод execute, який виконується
- рядок 15: він очищає словник сеансу. Як наслідок, список симуляцій, що знаходяться в сеансі, зникне. Це пояснює екран № 3.
- рядок 16: він повертає ключ success, який, як ми бачили, відобразить вигляд Tiles saisie [2].
19.15. Conclusion
Ми повністю прокоментували версію 1 нашого прикладу, який працює з імітованим шаром [metier]:
![]() |
Нам залишається лише «підключити» реальний бізнес-шар до шару [web].






























