7. Aplikacja QuiEst
Opisujemy tutaj aplikację Struts nieco bardziej zaawansowaną niż poprzednie, które musiały być proste, aby służyć celom dydaktycznym.
7.1. Klasa users
Dysponujemy klasą Java przechowującą informacje o użytkownikach komputera z systemem Unix. Dane te są zapisane w trzech konkretnych plikach:
- /etc/passwd: lista użytkowników
- /etc/group: lista grup
- /etc/aliases: lista aliasów pocztowych
Zawartość tych trzech plików jest następująca:
- /etc/passwd
Wiersze w tym pliku mają następującą postać:
login:pwd:uid:gid:id:dir:shell
z
nazwa użytkownika | |
jego zaszyfrowane hasło | |
jego numer użytkownika | |
jego numer grupy | |
jego tożsamość | |
jego katalog logowania | |
jego interpreter poleceń |
W ten sposób wiersz użytkownika mógłby wyglądać następująco:
Poprzedni użytkownik ma numer 110 i należy do grupy 57. Definicję grupy 57 można znaleźć w pliku /etc/group.
- /etc/group
Wiersze w tym pliku mają następującą postać:
nomGroupe:pwd:gid:membre1,membre2,....
gdzie
nazwa grupy | |
zaszyfrowane hasło – najczęściej to pole jest puste | |
numer grupy | |
loginy użytkowników – to pole może być puste |
W ten sposób wiersz dotyczący poprzedniej grupy 57 mógłby wyglądać następująco:
co oznacza, że grupa 57 nosi nazwę iup2-auto.
- /etc/aliases
Wiersze w tym pliku mają następującą postać:
z
alias | |
jednym lub kilkoma znakami tabulacji | |
nazwa użytkownika, do którego należy alias |
Zatem wiersz
guillaume.dupond: dupond
oznacza, że alias guillaume.dupond należy do użytkownika o nazwie logowania dupond. Przypomnijmy, że aliasy są wykorzystywane w adresach e-mail. Jeśli więc w poprzednim przykładzie komputer z systemem Unix nosi nazwę shiva.istia.univ-angers.fr, wiadomość adresowana do guillaume.dupond@shiva.istia.univ-angers.fr zostanie dostarczona do skrzynki pocztowej użytkownika o nazwie logowania dupond na tym komputerze.
W tym miejscu nie będziemy zajmować się całym interfejsem klasy **users**, a jedynie jej konstruktorem i kilkoma metodami:
import java.io.*;
import java.util.*;
public class users{
// atrybuty
private Hashtable usersByLogin=new Hashtable(); // login --> login, hasło, ..., katalog
private ArrayList erreurs=new ArrayList(); // lista komunikatów o błędach
....
// konstruktor
public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
// usersFileName: nazwa pliku użytkowników zawierającego wiersze w postaci
// login:pwd:uid:gid:id:dir:shell
// groupsFileName: nazwa pliku grup zawierającego wiersze w formacie
// nazwa:hasło:numer:członek1,członek2,..
// aliasesFileName: nazwa pliku zawierającego aliasy z wierszami o postaci
// alias:[tab]login
// tworzy słownik usersByLogin
....
}// konstruktor
// lista użytkowników
public Hashtable getUsersByLogin(){
return usersByLogin;
}
// błędy
public ArrayList getErreurs(){
return erreurs;
}
słownik (tablica skrótów), którego kluczami są nazwy użytkowników z pliku passwd. Wartością powiązaną z kluczem jest tablica ciągów znaków (String [7]), której elementami jest 7 pól wiersza pliku passwd powiązanego z daną nazwą użytkownika. Niektóre pola mogą być puste, jeśli wiersz zawiera mniej niż 7 pól. | |
lista komunikatów o błędach – pusta, jeśli nie ma błędów |
7.2. Aplikacja internetowa, która jest
Proponujemy stworzyć następującą aplikację internetową (stronę z formularzem):
![]() |
nr | nazwa | typ HTML | rola |
1 | cmbLogins | <select ...>...</select> | przedstawia listę wszystkich loginów, o których można uzyskać informacje |
2 | btnChercher | <input type="submit" ...> | służy do uruchomienia wyszukiwania |
Gdy użytkownik kliknie przycisk [Chercher] (2), logowanie z pola (1) jest wysyłane do obiektu U typu users. Jeśli logowanie istnieje, otrzymujemy następującą odpowiedź (strona informacyjna):
![]() |
Jak widać powyżej na przykładzie URL z przeglądarki, parametry formularza są wysyłane na serwer za pomocą GET. Można zatem bezpośrednio przekazać przeglądarce ten skonfigurowany URL. Właśnie to tutaj robimy, aby wprowadzić login, który nie istnieje. Otrzymujemy następującą odpowiedź (strona błędów):
![]() |
7.3. Architektura aplikacji
![]() |
W tej architekturze znajdujemy następujące komponenty:
- widoki:
- logins.jsp, służąca do wyświetlania listy logowań (widok 1)
- infos.jsp, służąca do wyświetlania informacji dotyczących loginu (widok 2)
- erreurs.jsp służąca do wyświetlania listy błędów (widok 3)
- formularze typu ActionForm wykorzystywane przez akcje:
- formLogins służy do zbierania danych z formularza logins.jsp
- akcje:
- SetupLoginAction, która przygotowuje zawartość formulaire.jsp, a następnie wyświetla ten widok
- InfosLoginAction, który przetwarza zawartość logins.jsp po jej wysłaniu na serwer
- ForwardAction, która przetwarza link [Retour vers le formulaire] z widoków infos.jsp i erreurs.jsp
- klasa biznesowa „users” wykorzystywana przez akcje do pobierania danych
- model zapewniany przez trzy pliki tekstowe: „passwd”, „group” i „aliases”
7.4. Pliki konfiguracyjne aplikacji internetowej
7.4.1. Plik server.xml
Kontekst aplikacji będzie nosił nazwę /strutsquiest2. W związku z tym dodamy następujący wiersz do pliku server.xml serwera Tomcat:
Po wykonaniu tej czynności ewentualnie uruchomimy ponownie Tomcat, aby uwzględnił nowy kontekst. Możemy sprawdzić jego poprawność, wywołując adres http://localhost:8080/strutsquiest2.
7.4.2. Plik web.xml
Plik konfiguracyjny aplikacji web.xml będzie wyglądał następująco:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
<servlet>
<servlet-name>strutsquiest2</servlet-name>
<servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>passwdFileName</param-name>
<param-value>data/passwd</param-value>
</init-param>
<init-param>
<param-name>groupFileName</param-name>
<param-value>data/group</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>strutsquiest2</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
Ten plik web.xml wprowadza nowość. Kontroler Struts nie nosi już nazwy org.apache.struts.action.ActionServlet, lecz jest to klasa pochodna, którą nazwaliśmy tutaj istia.st.struts.quiest.Quiest2ActionServlet. Pozwoli nam to pobrać dwa parametry inicjalizacyjne: passwdFileName (lokalizacja pliku passwd) oraz groupFileName (lokalizacja pliku group). Plik aliases jest zbędny w tej aplikacji.
7.4.3. Plik struts-config.xml
Plik struts-config.xml będzie wyglądał następująco:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="formLogins" type="org.apache.struts.action.DynaActionForm">
<form-property name="cmbLogins" type="java.lang.String" initial=""/>
<form-property name="tLogins" type="java.lang.String[]"/>
</form-bean>
</form-beans>
<action-mappings>
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
<action
path="/retourLogins"
parameter="/vues/logins.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
</action-mappings>
<message-resources
parameter="istia.st.struts.quiest.ApplicationResources"
null="false"
/>
</struts-config>
Znajdują się tu trzy główne sekcje:
- deklarację formularzy w sekcji <form-beans>
- deklaracja akcji w sekcji <action-mappings>
- deklaracja pliku zasobów w elemencie <message-ressources>
7.4.4. Obiekty (beany) formularzy aplikacji
<form-beans>
<form-bean name="formLogins" type="org.apache.struts.action.DynaActionForm">
<form-property name="cmbLogins" type="java.lang.String" initial=""/>
<form-property name="tLogins" type="java.lang.String[]"/>
</form-bean>
</form-beans>
W naszej aplikacji występuje tylko jeden bean formularza o nazwie formLogins, którego typ wywodzi się od DynaActionForm. Będzie on wykorzystywany w następujących sytuacjach:
- przechowywanie danych niezbędnych do wyświetlenia widoku nr 1
- pobieranie wartości z formularza widoku nr 1 po jego zatwierdzeniu (submit) przez użytkownika
Struktura bean’a formLogins jest powiązana z formularzem w widoku nr 1. Przyjrzyjmy się jej:
![]() |
nr | nazwa | typ HTML | rola |
1 | cmbLogins | <select ...>...</select> | przedstawia listę wszystkich loginów, o których można uzyskać informacje |
2 | btnChercher | <input type="submit" ...> | aby rozpocząć wyszukiwanie |
Rozróżnijmy kilka przypadków:
- z klienta do serwera – obiekt formLogins służy do przechowywania wartości z powyższego formularza HTML, który zostanie wysłany za pomocą przycisku [Envoyer]. Potrzebne jest zatem pole cmbLogins, które przyjmie wartość pola HTML, cmbLogins oraz c.a.d – czyli login wybrany przez użytkownika.
- Podczas przesyłania danych z serwera do klienta obiekt formLogins służy do przekazania początkowej zawartości widoku nr 1. Jego pole tLogins będzie stanowić zawartość listy 1. Jego pole cmbLogins pozwoli określić element listy 1, który ma zostać zaznaczony.
7.4.5. Akcje aplikacji
Akcje są realizowane przez obiekty typu Action lub pochodne. Konfiguracja akcji odbywa się wewnątrz tagów <action-mappings>:
<action-mappings>
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
<action
path="/retourLogins"
parameter="/vues/logins.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
</action-mappings>
Akcja /init
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
Opiszmy działanie akcji /init:
- Operacja /init odbywa się zazwyczaj tylko raz podczas pierwszego cyklu żądanie-odpowiedź, w którym użytkownik wysyła żądanie dotyczące obiektu URL http://localhost:8080/strutsquiest2/init.do
- obiekt formsLogins jest tworzony lub ponownie wykorzystywany. Jest on pobierany (ponowne wykorzystanie) lub umieszczany (tworzenie) w sesji zgodnie z atrybutem scope.
- Wywoływana jest jego metoda reset. Należy pamiętać, że domyślnie metoda ta nie wykonuje żadnych czynności w klasach ActionForm i klasach od nich pochodnych. Jest ona wywoływana tuż przed skopiowaniem danych z żądania klienta do obiektu ActionForm i służy do wyczyszczenia obiektu przed tym skopiowaniem. Jakie jest w tym przypadku żądanie klienta? Akcja /init jest uruchamiana, gdy żądany adres URL to http://localhost:8080/strutsquiest2/init.do. Ten obiekt URL może być wywołany przez obiekt GET lub POST. Wystarczy, że w tym żądaniu umieści się parametry o nazwach pól z formLogins, aby zostały one zainicjowane, jak pokazuje poniższy przykład:

- zapytanie zawiera parametr cmbLogins (afterpak). Kontroler Struts skopiował zatem wartość tego parametru do pola cmbLogins w formLogins. Następnie przeprowadzono akcję SetupLoginsAction, która zakończyła się wyświetleniem widoku logins.jsp. Widok ten zawiera formularz, w którym niektóre pola pobierają swoje wartości z formLogins. W ten sposób pole wyboru HTML o nazwie cmbLogins otrzymało swoją wartość z pola cmbLogins (=afterpak) z formLogins. Dlatego lista loginów pojawia się z pozycją ustawioną na loginie afterpak.
- Można by również dla zabawy przekazać parametr tLogins w następujący sposób:
Spowodowałoby to zainicjowanie pola tLogins o wartości formLogins wraz z tablicą {"login1","login2"}. Jednak, jak zobaczymy dalej, akcja SetupLoginsAction nadaje wartość polu tLogins i zastępuje utworzoną w ten sposób tablicę nową tablicą. To właśnie ta ostatnia pojawia się zatem w widoku logins.jsp.
- Powyższa dyskusja, choć nieco skomplikowana, ma tę zaletę, że pokazuje, iż nie można zakładać, że akcja /init zostanie wywołana bez parametrów pochodzących od klienta. Dlatego też przydatne może być użycie metody reset w celu wyczyszczenia pola formLogins. W takim przypadku musielibyśmy utworzyć klasę pochodną od klasy DynaActionForm. Nie zrobiliśmy tego w tym przypadku.
- Po wywołaniu metody reset klasy formLogins kontroler kopiuje dane z żądania klienta do pól o tej samej nazwie w klasie formLogins. Zazwyczaj akcja /init jest wywoływana bez parametrów od klienta, ale jak wykazaliśmy wcześniej, nic nie stoi na przeszkodzie, aby klient wywołał akcję /init z dowolnymi parametrami. Po zakończeniu tej fazy pola cmbLogins i tLogins mogą zatem z całą pewnością mieć przypisane wartości. Widzieliśmy, że pole cmbLogins zachowałoby tę wartość, ale nie pole tLogins.
- Następnie kontroler sprawdza atrybut `validate` akcji. W tym przypadku ma on wartość „false”. Metoda `validate` dla pola formLogins nie zostanie wywołana. Nie będziemy jej więc pisać.
- Obiekt SetupLoginsAction jest tworzony lub odnawiany, jeśli już istniał, a następnie uruchamiana jest jego metoda `execute`. Jego jedyną rolą jest przypisanie wartości do pola tLogins w obiekcie formLogins. Wartością tą jest tablica loginów, o którą zostanie zwrócona prośba do klasy biznesowej users. Operacja ta może zakończyć się niepowodzeniem. Dlatego po akcji /init mogą następować dwa widoki:
- widok erreurs.jsp, jeśli klasa „users” nie była w stanie dostarczyć tablicy loginów
- widok logins.jsp w przeciwnym razie
- kontroler wyświetli jeden z tych dwóch widoków
- cykl żądania-odpowiedzi akcji /init został zakończony.
Akcja /infosLogin
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
Opiszmy działanie akcji / infosLogin:
- akcja /infosLogin jest zwykle uruchamiana, gdy użytkownik kliknie przycisk [Chercher] w widoku logins.jsp. Następnie wysyłane jest żądanie do serwera, określone przez tag HTML <form> w widoku:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
- widać, że żądanie jest wysyłane do serwera metodą GET. Użytkownik może zatem wpisać je ręcznie:

- obiekt formsLogins jest tworzony lub ponownie wykorzystywany. Jest on pobierany (ponowne wykorzystanie) lub umieszczany (tworzenie) w sesji zgodnie z atrybutem scope.
- Jego metoda reset jest wywoływana tuż przed skopiowaniem danych z żądania klienta do obiektu ActionForm. Zazwyczaj ma ona postać http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=xx, gdzie xx to login wybrany z listy loginów. Może to jednak być dowolna wartość, jeśli użytkownik skorzystał z poprzedniego obiektu URL, przekazując dowolne parametry. Rozważmy następującą sekwencję stron:

- akcja /infosLogin została wywołana z ciągiem parametrów cmbLogins=xx&tLogins=login1&tLogins=login2. Pola cmbLogins i tLogins w formLogins otrzymają zatem odpowiednio wartości „xx” oraz {"login1","login2"}. Akcja /infosLogin zwróci się do klasy biznesowej users o informacje związane z loginem „xx”. Klasa users odpowie, że ten login nie istnieje. Stąd widok przedstawiony powyżej. Teraz skorzystajmy z powyższego linku [Retour au formulaire]:

- To akcja /retourLogins jest uruchamiana przez link [Retour au formulaire]. Akcja ta po prostu wyświetla widok logins.jsp bez żadnych czynności pośrednich. Przypomnijmy, że pole tLogins służy do zasilania listy loginów w widoku logins.jsp. Ponieważ użytkownik zmienił tę wartość na {"login1","login2"}, to właśnie te dwa loginy pojawiają się teraz na liście. Po raz kolejny należy podkreślić absolutną konieczność uwzględnienia w działaniu aplikacji przypadków dowolnych parametrów ustalanych przez użytkownika lub program. Rozwiązaniem przedstawionego tutaj problemu byłoby skierowanie linku [Retour au formulaire] do akcji /init. W ten sposób mielibyśmy pewność, że otrzymamy prawidłową listę loginów.
- Wróćmy do zwykłego żądania skierowanego do akcji /infosLogin, na przykład:
http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=afterpak
- Kontroler Struts przypisze wartość do pola cmbLogins obiektu ActionForm. Natomiast pole tLogins nie zostanie przypisane (w wysłanym zapytaniu nie ma odpowiadającego mu pola). Takie działanie nam odpowiada. Nie będziemy więc musieli pisać własnej metody resetującej dla obiektu formLogins.
- Po wywołaniu metody reset obiektu formLogins kontroler kopiuje dane z żądania klienta do pól o tej samej nazwie w obiekcie formLogins. Pole cmbLogins otrzyma wartość – login wybrany przez użytkownika (afterpak).
- Następnie kontroler sprawdza atrybut „validate” akcji. W tym przypadku ma on wartość „false”. Metoda „validate” obiektu formLogins nie zostanie wywołana.
- Obiekt InfosLoginAction zostaje utworzony lub odnowiony, jeśli już istniał, a następnie uruchamiana jest jego metoda execute. Jego zadaniem jest pobranie informacji związanych z loginem cmbLogins. Informacje te zostaną pobrane z klasy biznesowej users. Operacja ta może zakończyć się niepowodzeniem (na przykład w przypadku nieistniejącego loginu). Dlatego po akcji /infosLogin mogą następować dwa widoki:
- widok erreurs.jsp, jeśli klasa „users” nie była w stanie dostarczyć żądanych informacji
- widok infos.jsp w przeciwnym razie
- kontroler wyświetli jeden z tych dwóch widoków
- cykl żądania-odpowiedzi akcji /infosLogin został zakończony.
Akcja /retourLogins
<action
path="/retourLogins"
parameter="/vues/logins.jsp"
type="org.apache.struts.actions.ForwardAction"
/>
- Akcja /retourLogins jest uruchamiana poprzez aktywację linku [Retour au formulaire] w widokach erreurs.jsp i infos.jsp.
- W tym przypadku nie ma formularza powiązanego z akcją. Przechodzi się zatem bezpośrednio do wykonania metody `execute` obiektu `ForwardAction`, która zwróci obiekt `ActionForward` wskazujący na widok `/vues/logins.jsp`.
7.4.6. Plik komunikatów aplikacji
Trzecia sekcja pliku struts-config.xml to sekcja pliku komunikatów:
Plik ApplicationResources.properties znajduje się w katalogu WEB-INF/classes/istia/st/struts/quiest. Jego zawartość jest następująca:
errors.header=<ul>
errors.footer=</ul>
parametreManquant=<li>Le paramètre [{0}] n'a pas été initialisé</li>
usersException=<li>Erreur d'initialisation de l'application : {0}</li>
loginInconnu=<li>Le login [{0}] n'existe pas</li>
7.5. Kod widoków
Jeśli czytelnik nie rozumie kodu widoków przedstawionych poniżej, prosimy o ponowne zapoznanie się z lekcją dotyczącą obsługi formularzy.
7.5.1. Widok logins.jsp
Przypomnijmy, że ten widok jest wyświetlany w dwóch przypadkach:
- przy wywołaniu akcji /init podczas pierwszego cyklu żądanie-odpowiedź
- przy wywołaniu akcji /retourLogins w kolejnych cyklach
Kod widoku logins.jsp jest następujący:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Quiest - formulaire</title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<center>
<h2>Application QuiEst</h2>
<hr>
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
<table>
<tr>
<td>Login cherché</td>
<td>
<html:select name="formLogins" property="cmbLogins">
<html:options name="formLogins" property="tLogins"/>
</html:select>
</td>
<td>
<html:submit value="Chercher"/>
</td>
</tr>
</table>
</html:form>
</center>
</body>
</html>
7.5.2. Widok infos.jsp
Ten widok jest wyświetlany po pomyślnym wywołaniu akcji /infosLogin. Jego kod jest następujący:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<head>
<title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
<hr>
<table border="1">
<tr>
<th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
</tr>
<tr>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>
</tr>
</table>
<br>
<html:link page="/retourLogins.do">
Retour au formulaire
</html:link>
</body>
</html>
Ten widok wykorzystuje obiekt o nazwie infosLoginBean, umieszczony w zapytaniu przez akcję /infosLogin. Obiekt ten posiada dwa pola:
String titre; // tytuł do wyświetlenia w widoku
String[] infosLogin; // tabela informacji do wyświetlenia w widoku
Szczegółowo omówimy tę klasę, gdy przejdziemy do kodu klasy InfosLoginAction.
7.5.3. Widok erreurs.jsp
Ten widok jest wyświetlany, gdy operacje /init lub /infosLogin zakończą się błędem. Jego kod wygląda następująco:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Application QuiEst - erreurs</title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<h2 align="center">Application QuiEst - Erreurs</h2>
<hr>
<h2>Les erreurs suivantes se sont produites</h2>
<html:errors/>
<html:link page="/retourLogins.do">
Retour au formulaire
</html:link>
</body>
</html>
7.6. Klasy Java
Plik web.xml odwołuje się do klasy Java:
<web-app>
<servlet>
<servlet-name>strutsquiest2</servlet-name>
<servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
....
</servlet>
...
</web-app>
Plik konfiguracyjny struts-config.xml odwołuje się do dwóch klas Java:
<action
path="/infosLogin"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.InfosLoginAction"
>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
<forward name="afficherInfos" path="/vues/infos.jsp"/>
</action>
<action
path="/init"
name="formLogins"
validate="false"
scope="session"
type="istia.st.struts.quiest.SetupLoginsAction"
>
<forward name="afficherLogins" path="/vues/logins.jsp"/>
<forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
</action>
7.6.1. Klasa Quiest2ActionServlet
Klasa Quiest2ActionServlet wywodzi się z klasy ActionServlet, czyli klasy kontrolera Struts. Tworzymy klasę pochodną ActionServlet, aby dostosować jej metodę init. Metoda ta, wykonywana tylko raz podczas początkowego ładowania serwletu, pozwoli nam bowiem utworzyć obiekt biznesowy typu users. Obiekt ten musi zostać utworzony tylko raz, a metoda init jest odpowiednim miejscem do wykonania tej operacji. Obiekt typu „users” wymaga dwóch plików do utworzenia: plików „passwd” i „group”. Lokalizacja tych dwóch plików jest przekazywana jako parametry do serwletu w pliku web.xml aplikacji:
<servlet>
<servlet-name>strutsquiest2</servlet-name>
<servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>passwdFileName</param-name>
<param-value>data/passwd</param-value>
</init-param>
<init-param>
<param-name>groupFileName</param-name>
<param-value>data/group</param-value>
</init-param>
</servlet>
Kod serwletu wygląda następująco:
package istia.st.struts.quiest;
import java.util.*;
import javax.servlet.*;
import org.apache.struts.action.*;
import istia.st.users.*;
public class Quiest2ActionServlet
extends ActionServlet {
// atrybuty serwletu
private users u = null;
private ActionErrors erreurs = new ActionErrors();
private String[] tLogins;
//init
public void init() throws ServletException {
// nie zapomnij zainicjować klasy nadrzędnej
super.init();
// zmienne lokalne
final String[] initParams = {"passwdFileName", "groupFileName"};
Properties params = new Properties();
// pobieramy parametry inicjalizacyjne serwletu
ServletConfig config = getServletConfig();
String servletPath = config.getServletContext().getRealPath("/");
for (int i = 0; i < initParams.length; i++) {
String valeur = config.getInitParameter(initParams[i]);
if (valeur == null) {
erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("parametreManquant", initParams[i]));
valeur = "";
}
// zapisujemy parametr
params.setProperty(initParams[i], valeur);
} //for
// powrót, jeśli wystąpiły błędy inicjalizacji
if (erreurs.size() != 0) {
return;
}
// tworzy się obiekt users
try {
u = new users(servletPath + "/" + params.getProperty("passwdFileName"),
servletPath + "/" + params.getProperty("groupFileName"), null);
}
catch (Exception ex) {
erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("usersException", ex.getMessage()));
return;
} //catch
// pobieramy listę loginów
tLogins = new String[u.getUsersByLogin().size()];
Enumeration eLogins = u.getUsersByLogin().keys();
for (int i = 0; i < tLogins.length; i++) {
tLogins[i] = (String) eLogins.nextElement();
}
// sortujemy nazwy użytkowników
Arrays.sort(tLogins);
} //init
// metoda dostępu do prywatnych informacji serwletu
public Object[] getInfos() {
return new Object[] {erreurs, u, tLogins};
}
}
W skrócie, działanie metody init wygląda następująco:
- najpierw wywoływana jest metoda init klasy nadrzędnej (ActionServlet), aby ta mogła się poprawnie zainicjować
- następnie odczytywane są parametry inicjalizacji. Jeśli którychś brakuje, wypełniany jest prywatny atrybut ActionErrors „błędy”.
- jeśli parametry inicjalizacji są obecne, tworzony jest obiekt `users`. Tworzenie tego obiektu może spowodować wygenerowanie wyjątku. W takim przypadku wypełniany jest atrybut `ActionErrors` `erreurs`.
- Jeśli tworzenie przebiegło pomyślnie, z utworzonego obiektu pobierana jest lista wszystkich loginów, a następnie jest ona sortowana do tablicy, którą umieszcza się w prywatnym atrybucie String[] tLogins.
- Utworzony obiekt users jest zapisywany w prywatnym atrybucie users u.
- Metoda publiczna getInfos pozwala uzyskać trzy atrybuty prywatne (u, błędy, tLogins) w postaci tablicy obiektów.
7.6.2. Klasa SetupLoginsAction
Celem tej akcji jest zainicjowanie obiektu DynaActionForm formLogins. Obiekt ten, umieszczony w sesji, nie będzie już wymagał ponownego inicjowania w przyszłości. Akcja SetupLoginsAction odbywa się zatem tylko raz. Jej kod jest następujący:
package istia.st.struts.quiest;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public class SetupLoginsAction
extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {
// przygotowuje formularz do wyświetlenia
// pobieranie informacji z serwletu kontrolera
// informacje=(ActionErrors błędy, użytkownicy u, String[] tLogins)
Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();
// czy wystąpiły błędy podczas inicjalizacji?
ActionErrors erreurs = (ActionErrors) infos[0];
if (!erreurs.isEmpty()) {
this.saveErrors(request, erreurs);
return mapping.findForward("afficherErreurs");
}
// w formularzu wpisujemy dane logowania
DynaActionForm formLogins=(DynaActionForm) form;
formLogins.set("tLogins",infos[2]);
return mapping.findForward("afficherLogins");
}
}
Podobnie jak w przypadku wszystkich akcji Struts, kod znajduje się w metodzie execute. Metoda ta:
- pobiera z kontrolera Struts informacje, które ten zapisał za pomocą swojej metody init. Umożliwia to metoda getServlet() klasy Action.
- wśród nich znajduje się atrybut ActionErrors – błędy kontrolera. Jeśli ta lista błędów nie jest pusta, jest ona umieszczana w zapytaniu i wyświetlany jest widok erreurs.jsp.
- Jeśli lista błędów jest pusta, wówczas do pola tLogins w bean formLogins przypisywana jest lista logowań utworzona początkowo przez kontroler. Następnie wywoływane jest wyświetlenie widoku logins.jsp, który wyświetli listę logowań.
7.6.3. Klasy InfosLoginBean i InfosLoginAction
Akcja InfosLoginAction ma na celu pobranie informacji związanych z loginem wybranym przez użytkownika i przedstawienie ich użytkownikowi. Informacje te zostaną zebrane w obiekcie typu InfosLoginBean:
package istia.st.struts.quiest;
public class InfosLoginBean implements java.io.Serializable{
// bean zawierający informacje niezbędne dla strony informacyjnej
private String titre;
private String[] infosLogin;
// konstruktor
public InfosLoginBean(String titre, String[] infosLogin){
this.titre=titre;
this.infosLogin=infosLogin;
}
// metody pobierające
public String getTitre(){
return this.titre;
}
public String[] getInfosLogin(){
return this.infosLogin;
}
public String getInfosLogin(int i){
return this.infosLogin[i];
}
}
Poprzednia klasa to bean o nazwie c.a.d. Jest to klasa Java, w której prywatny atrybut T unAttribut jest automatycznie powiązany z dwiema metodami prywatnymi:
- void setUnAttribut(T wartość){unAttribut=wartość;}
- T getUnAttribut(){ return unAttribut;}
Warto zwrócić uwagę na specjalną składnię metod get i set. Jeśli atrybutem jest tablica T[] unAttribut, można utworzyć metody get i set dla elementów tej tablicy:
- void setUnAttribut(T wartość, int i){unAttribut[i]=wartość;}
- T getUnAttribut(int i){ return unAttribut[i];}
Aby lepiej to zrozumieć, przyjrzyjmy się kodowi widoku infos.jsp, który powinien zostać wysłany w następstwie akcji InfosLoginAction:
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<head>
<title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
</head>
<body background="<html:rewrite page="/images/standard.jpg"/>">
<h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
<hr>
<table border="1">
<tr>
<th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
</tr>
<tr>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
<td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>
</tr>
</table>
<br>
<html:link page="/retourLogins.do">
Retour au formulaire
</html:link>
</body>
</html>
Weźmy następujący tag:
Żądanie dotyczy zapisania wartości pola „title” (property) obiektu infosLoginBean (name) umieszczonego w zapytaniu (scope). Wartość do zapisania zostanie uzyskana za pomocą request.getAttribute("infosLoginBean").getTitre(). Musi zatem istnieć metoda getTitre w klasie InfosLoginBean. Tak właśnie jest. Tag
żąda zapisania wartości elementu infosLogin[0] obiektu infosLoginBean podanego w zapytaniu. Wartość do zapisania zostanie uzyskana za pomocą request.getAttribute("infosLoginBean").getInfosLogin(0). W związku z tym w klasie InfosLoginBean musi istnieć metoda getInfosLogin(int i). Tak właśnie jest.
Klasa InfosLoginAction służy do utworzenia poprzedniego obiektu InfosLoginBean na podstawie loginu wybranego przez użytkownika. Jej kod wygląda następująco:
package istia.st.struts.quiest;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import istia.st.users.*;
public class InfosLoginAction
extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {
// musi wyświetlać informacje związane z danymi logowania
// pobieramy informacje z serwletu kontrolera
// informacje=(ActionErrors błędy, użytkownicy u, LoginBean[] tLogins)
Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();
// czy wystąpiły błędy podczas inicjalizacji?
ActionErrors erreurs = (ActionErrors) infos[0];
if (!erreurs.isEmpty()) {
this.saveErrors(request, erreurs);
return mapping.findForward("afficherErreurs");
}
// najpierw pobierz ten login
String login = (String) ( (DynaActionForm) form).get("cmbLogins");
// czy jest coś?
if (login == null) {
// To nie w porządku – odsyłamy formularz z danymi logowania
DynaActionForm formLogins=(DynaActionForm) form;
formLogins.set("tLogins",infos[2]);
return mapping.findForward("afficherLogins");
}
// mamy login – szukamy go
String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);
// czy znaleźliśmy?
if (infosLogin == null) {
// nazwa użytkownika nie została znaleziona – wyświetlamy stronę błędów
ActionErrors erreurs2=new ActionErrors();
erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
this.saveErrors(request, erreurs2);
return mapping.findForward("afficherErreurs");
}
// nazwa użytkownika została znaleziona – umieszczamy znalezione informacje w żądaniu
String titre="Application QuiEst - login["+login+"]";
InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
request.setAttribute("infosLoginBean",infosLoginBean);
return mapping.findForward("afficherInfos");
}
}
Działanie metody **execute** przebiega następująco:
- pobierane są informacje zebrane przez kontroler Struts podczas jego inicjalizacji. Jeśli kontroler odnotował błędy, wykonanie zatrzymuje się w tym momencie i wyświetlane jest okno z listą tych błędów.
- sprawdza się, czy podano login. Jeśli użytkownik skorzystał z formularza wyboru loginu, login jest obecny. Użytkownik może jednak równie dobrze wpisać adres URL akcji bezpośrednio w przeglądarce, bez przekazywania parametrów. Jeśli nie ma loginu, wyświetlana jest ponownie lista loginów.
- Jeśli login istnieje, pobierane są informacje powiązane z klasą biznesową users. Jeśli klasa ta nie znajdzie szukanego loginu, wyświetlana jest strona błędów. W przeciwnym razie tworzony jest obiekt InfosLoginBean, w którym umieszczane są informacje potrzebne widokowi infos.jsp. Obiekt ten jest umieszczany w żądaniu, a następnie wyświetlana jest strona infos.jsp.
7.7. Wdrożenie
Struktura drzewa aplikacji wygląda następująco:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
7.8. Wnioski
Wykorzystaliśmy Struts w realistycznej aplikacji opartej na klasie biznesowej. Pokazaliśmy również, że należy zwracać szczególną uwagę na żądanie wysyłane przez klienta i nie przyjmować żadnych założeń dotyczących jego charakteru. Żądanie może mieć dowolną postać, a każda aplikacja musi najpierw sprawdzić jego poprawność.











