Skip to content

2. Pierwszy przykład

Większość naszych przykładów będzie ograniczać się wyłącznie do warstwy internetowej zaimplementowanej przy użyciu Struts 2:

Gdy opanujemy podstawy, przeanalizujemy bardziej złożony przykład z architekturą wielowarstwową.

2.1. Tworzenie przykładu

Tworzymy naszą pierwszą aplikację.

  • w [1] tworzymy nowy projekt
  • w [2] wybieramy typ Java Web / Web Application
  • w [3] nadajemy nazwę projektowi
  • w [4] wskazujemy lokalizację projektu.
  • w [5] ustalamy nowy projekt jako projekt główny.
  • w [6] wybieramy serwer Tomcat. Podczas instalacji NetBeans 7.01 zainstalowano dwa serwery: Apache Tomcat i GlassFish 3.1.
  • W oknie [7] wskazujemy, że będziemy pracować z frameworkiem Struts 2. To właśnie instalacja wtyczki dla Struts 2 daje nam tę możliwość. Bez tej wtyczki framework Struts 2 nie jest nam proponowany.
  • W oknie [8] należy poprosić o utworzenie przykładowego projektu, który będziemy analizować.
  • W pliku [9] można sprawdzić, które biblioteki Struts 2 będą wykorzystywane.
  • w [10] – wygenerowany projekt. Wrócimy do niego później.
  • W pliku [11] znajdują się biblioteki projektu. Zostały one zintegrowane przez wtyczkę Struts 2. Jeśli nie dysponujemy tą wtyczką, biblioteki te można znaleźć w folderze [lib] pobranej dystrybucji Struts 2. Następnie należy wykonać kroki 12 i 13.

2.2. Projekt wygenerowany w systemie plików

  • w [1], zakładka [Projects] przedstawia widok „programisty” projektu
  • w [2], zakładka [Files] przedstawia folder projektu w systemie plików
  • w [2A] gałąź [Web Pages] jest reprezentowana w [2] przez folder [web] [2B]
  • w [3A] gałąź [Source Packages] jest reprezentowana w [2] przez folder [java] [3B]

2.3. Plik konfiguracyjny [META-INF/context.xml]

Plik ten ma następującą treść:


<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/exemple-01"/>

Wiersz 2 wskazuje, że kontekstem aplikacji internetowej jest /exemple-01. Wszystkie pliki typu [http://machine:port/exemple-01/...] o numerze URL będą przetwarzane przez tę aplikację. Kontekst ten można znaleźć we właściwościach projektu [2]: kliknij prawym przyciskiem myszy na projekcie / Properties / Run.

2.4. Plik konfiguracyjny [WEB-INF/web.xml]

Każda aplikacja internetowa jest konfigurowana za pomocą pliku [web.xml] znajdującego się w folderze [WEB-INF] aplikacji. Wygenerowany plik ma następującą treść:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <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>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>example/HelloWorld.JSP</welcome-file>
    </welcome-file-list>
</web-app>
  • Wiersze 3–6 definiują filtr zaimplementowany przez klasę [org.apache.struts2.dispatcher.FilterDispatcher] w Struts 2. To właśnie ta klasa będzie pełnić rolę kontrolera C dla modelu MVC.
  • Wiersze 7–10: definiują powiązanie między modelem URL a filtrem, który ma przetwarzać elementy URL zgodne z tym modelem. W tym miejscu wskazano, że każdy URL (szablon /*) musi być przetwarzany przez filtr o nazwie struts2. Jest to filtr zdefiniowany w wierszach 3–6. Zatem w tym przypadku wszystkie URL przejdą przez kontroler Struts 2.
  • wiersze 11–15: określają czas trwania sesji użytkownika, w tym przypadku 30 minut. Podczas kolejnych żądań użytkownik jest identyfikowany za pomocą tokenu sesji, który został mu przypisany przy pierwszym żądaniu i który następnie wysyła on systematycznie przy każdym nowym żądaniu. Pozwala to serwerowi internetowemu rozpoznać użytkownika i zarządzać jego „pamięcią”, zwaną sesją. Jeśli między dwoma żądaniami upłynie więcej niż 30 minut, generowany jest nowy token sesji dla użytkownika, który w ten sposób traci swoją „pamięć” i rozpoczyna nową sesję.
  • wiersze 16–18: określają plik, który ma zostać wyświetlony, gdy użytkownik wywołuje aplikację internetową bez żądania konkretnego dokumentu. Tak więc, gdy żądany plik URL to [http://machine:port/exemple-01], a serwowany plik to URL, wówczas serwowany plik będzie miał nazwę [http://machine:port/exemple-01/example/HelloWord.JSP].

2.5. Plik konfiguracyjny [struts.xml]

Plik [struts.xml] jest plikiem konfiguracyjnym Struts 2. Może znajdować się w dowolnym miejscu w katalogu ClassPath projektu. W powyższym projekcie NetBeans katalog ClassPath projektu składa się z dwóch gałęzi:

  • Pakiety źródłowe
  • Biblioteki

Każdy folder lub biblioteka znajdująca się w tych dwóch gałęziach stanowi zatem część struktury projektu ClassPath. Zazwyczaj plik [struts.xml] umieszcza się w <default package>. W tym przypadku jego zawartość jest następująca:


<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <include file="example.xml"/>
    <!-- Konfiguracja dla pakietu domyślnego. -->
    <package name="default" extends="struts-default">
    </package>
</struts>
  • wiersze 5 i 10: tagiem głównym dokumentu jest tag <struts>
  • wiersz 6: w tym miejscu umieszczany jest plik [example.xml]. Przynosi on zatem własną konfigurację. Wrócimy do tego później.
  • wiersze 8–9: definiują pakiet o nazwie „default”. Pakiet pozwala skonfigurować grupę akcji Struts 2 o tym samym identyfikatorze URL. Na przykład [/chemin/Action1] i [/chemin/Action2]. Można zatem zdefiniować pakiet dla tych akcji:

<package name="employes" namespace="/employes" extends="struts-default">
... configuration
    </package>

Powyższy pakiet nosi nazwę „pracownicy” i konfiguruje działania URL oraz /employes/Action. Pakiet ten może dziedziczyć po innym pakiecie za pomocą słowa kluczowego „extends”. W powyższym przykładzie pakiet employes dziedziczy po pakiecie struts-default. Pakiet ten znajduje się w pliku [struts-default.xml] w bibliotece struts2-core.jar:

Pakiet „struts-default” zdefiniowany w pliku [struts-default.xml] konfiguruje różne elementy, w tym listę interceptorów uruchamianych podczas wywołania akcji. Wróćmy do struktury MVC aplikacji Struts 2:

Aby przetworzyć żądanie URL o postaci [http://machine:port/.../Action], kontroler [FilterDispatcher] utworzy instancję klasy implementującej żądaną akcję i wykona jedną z jej metod, domyślnie metodę o nazwie execute. Wywołanie tej metody execute przejdzie przez szereg interceptorów:

Zarówno interceptory, jak i akcja przetwarzają to samo żądanie. Lista interceptorów zdefiniowana w pakiecie struts-default jest w większości przypadków wystarczająca. Dlatego nasze pakiety Struts będą zawsze rozszerzać pakiet struts-default. Aby dowiedzieć się, czym zajmują się poszczególne interceptory, należy zapoznać się z rozdziałem 4 dokumentu [ref2].

Wróćmy do pliku konfiguracyjnego struts.xml:


<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <include file="example.xml"/>
    <!-- Konfiguracja pakietu domyślnego. -->
    <package name="default" extends="struts-default">
    </package>
</struts>
  • wiersz 8: pakiet o nazwie default pełni szczególną rolę. Obsługuje on działania, które nie zostały skonfigurowane w innych pakietach. Tutaj, w wierszach 8–9, nie ma żadnej konfiguracji dla pakietu default. Można by zatem usunąć definicję tego pakietu.

Przyjrzyjmy się teraz plikowi [example.xml] dołączonemu w wierszu 6 pliku [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>
    <package name="example" namespace="/example" extends="struts-default">
        <action name="HelloWorld" class="example.HelloWorld">
            <result>/example/HelloWorld.JSP</result>
        </action>
    </package>
</struts>
  • wiersz 8: definiuje pakiet o nazwie example, który rozszerza pakiet struts-default. Ten pakiet obsługuje pliki URL typu /example/Action (przestrzeń nazw /example).
  • wiersze 9–11: definiują akcję o nazwie HelloWorld (atrybut name), która odpowiada zatem URL /example/Helloworld. Akcja ta jest przetwarzana przez instancję klasy example.HelloWorld (atrybut class).
    • Kontroler [FilterDispatcher] spowoduje wywołanie metody execute tej klasy.
    • Ta metoda zwróci ciąg znaków zwany kluczem nawigacyjnym.
    • Poszczególne klucze nawigacyjne należy zdefiniować za pomocą tagów <result name="klucz"/> (wiersz 10). W przypadku braku atrybutu name domyślnie używany jest klucz success. Tak jest w powyższym przypadku. Zatem metoda execute klasy example.HelloWorld musi zwrócić kontrolerowi [FilterDispatcher] klucz success.
    • Kontroler [FilterDispatcher] powoduje wówczas wyświetlenie strony [/example/HelloWorld.JSP] (wiersz 10).

Jeśli połączymy dwa pliki [struts.xml] i [example.xml], usuńmy pakiet default, który wydaje się zbędny, wszystko przebiega tak, jakby plik [struts.xml] został zredukowany do samego pliku [example.xml].

2.6. Akcja HelloWorld

Zgodnie z analizowanym plikiem [struts.xml] akcja HelloWorld jest uruchamiana, gdy plik URL żądany przez klienta to /example/HelloWorld. Wówczas wykonywana jest jej metoda execute. Musi ona zwrócić klucz nawigacyjny. Widzieliśmy, że istnieje tylko jeden: success, a zatem w odpowiedzi wysyłana była do użytkownika strona /example/HelloWorld.JSP.

Kod akcji HelloWorld wygląda następująco:

package example;

import com.opensymphony.xwork2.ActionSupport;

public class HelloWorld extends ActionSupport {

    public String execute() throws Exception {
        setMessage(getText(MESSAGE));
        return SUCCESS;
    }

    public static final String MESSAGE = "HelloWorld.message";

    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
  • wiersz 5: klasa HelloWorld wywodzi się z klasy ActionSupport biblioteki Struts 2. Tak jest praktycznie zawsze. Pozwala to na korzystanie z niektórych metod, takich jak metoda getText w wierszu 8.
  • wiersze 7–10: metoda execute, która jest wykonywana po wywołaniu kontrolera Struts. Wiadomo, że musi ona zwracać ciąg znaków, stąd jej sygnatura w wierszu 7. W tym przypadku zwraca ona stałą SUCCESS, zdefiniowaną również w ActionSupport. W ten sam sposób zdefiniowano inne stałe dla wyniku metody execute:
Constante
Valeur
SUCCESS
„success”
ERROR
„error”
INPUT
„input”
LOGIN
„login”

W tym przypadku metoda execute zwraca ciąg znaków success. Jeśli odwołamy się do pliku [struts.xml], to w odpowiedzi użytkownikowi zostanie zwrócona strona /example/HelloWorld.JSP.

  • wiersz 8: metoda execute inicjuje pole message z wiersza 14 wartością getText(" HelloWorld.message "). Metoda getText należy do klasy nadrzędnej ActionSupport. Pozwala ona na pobranie tekstu z pliku zgodnie z używanym językiem. Domyślnie zostanie tutaj użyty plik package.properties znajdujący się w tym samym pakiecie co akcja. Plik ten występuje w dwóch wersjach:

Plik [package.properties] ma następującą treść:

HelloWorld.message= Struts is up and running ...

Jest to ciąg wierszy tekstu w formacie clé=valeur. Jeśli zostanie użyty ten plik, wartość getText("HelloWorld.message") będzie brzmiała: Struts is up and running ...

Plik [package_es.properties] ma następującą postać:

HelloWorld.message= ¡Struts está bien! ...

Jeśli językiem używanym przez przeglądarkę klienta jest hiszpański (atrybut „es” w pliku package_es.properties), plik getText („HelloWorld.message”) będzie zawierał wartość ¡Struts está bien! ... We wszystkich pozostałych przypadkach zostanie użyty plik [package.properties].

2.7. Widok HelloWorld.JSP

To ostatni element układanki Struts. Jest to widok wyświetlany po wysłaniu żądania do pliku URL /example/HelloWorld. Widzieliśmy już, przez jaki labirynt przechodziło początkowe żądanie, aby w końcu wyświetlić tę odpowiedź. Kod strony wygląda następująco:


<%@ page contentType="text/html; charset=UTF-8" %>

<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
    <head>
        <title><s:text name="HelloWorld.message"/></title>
    </head>

    <body>
        <h2><s:property value="message"/></h2>

        <h3>Languages</h3>
        <ul>
            <li>
                <s:url id="URL" action="HelloWorld">
                    <s:param name="request_locale">en</s:param>
                </s:url>
                <s:a href="%{URL}">English</s:a>
            </li>

            <li>
                <s:url id="URL" action="HelloWorld">
                    <s:param name="request_locale">es</s:param>
                </s:url>

                <s:a href="%{URL}">Espanol</s:a>

            </li>
        </ul>
    </body>
</html>
  • strona wykorzystuje tagi HTML (wiersze 5, 6, ...) oraz tagi z biblioteki zdefiniowanej w wierszu 3. Wszystkie tagi <s:xx> należą do tej biblioteki.
  • wiersz 7: tag <s:text> pozwala wyświetlić inny tekst w zależności od języka przeglądarki klienta. Atrybut name wskazuje klucz, którego należy szukać w plikach komunikatów. Również w tym przypadku wykorzystywane będą pliki o nazwie package_xx.properties. Przypomnijmy, że zawierają one tylko jedną wiadomość o kluczu HelloWorld.message.
  • wiersz 11: znacznik <s:property name="właściwość"> pozwala zapisać wartość właściwości obiektu o nazwie ActionContext. W obiekcie tym znajdują się:
    • właściwości akcji, która została wykonana. name="message" wyświetli wartość pola „message” bieżącej akcji. Do uzyskania tej wartości lub jej zainicjowania wykorzystywane są metody get i set powiązane z tym polem. Metody te muszą zatem istnieć.
    • atrybuty bieżącego żądania oznaczone jako <s:property name="#request['clé']">
    • atrybuty sesji użytkownika oznaczone jako <s:property name="#session['clé']">
    • atrybuty samej aplikacji oznaczone jako <s:property name="#application['clé']">
    • parametry wysłane przez przeglądarkę klienta oznaczone jako <s:property name="#parameters['clé']">
    • notacja <s:property name="#attr['clé']"> wyświetla wartość obiektu wyszukanego kolejno na stronie, w zapytaniu, sesji i aplikacji.
  • wiersze 16–18: tag <s:url> służy do zdefiniowania URL. Atrybut id nadaje nazwę URL, który zostanie utworzony. Nazwa ta jest następnie wykorzystywana w wierszu 19. Atrybut action określa, do jakiej akcji ma prowadzić URL.
  • wiersz 17: tag <s:param ..> pozwala na dodanie parametrów do URL w postaci ?param1=valeur1&param2=valeur2&.... W tym przypadku dodanym parametrem będzie ?request_locale=es.

Ostatecznie wygenerowany plik URL będzie wyglądał następująco:

Aby zrozumieć ten kod URL, należy pamiętać, że strona [HelloWorld.JSP] jest wyświetlana w dwóch przypadkach:

  • (ciąg dalszy)
    • na bezpośrednie żądanie URL [/exemple-01/example/HelloWorld.JSP]
    • na żądanie akcji [/exemple-01/example/HelloWorld.action]

W obu przypadkach ścieżka do strony URL to /exemple-01/example.. Tag <s:url action= „... ”> dodaje do tej ścieżki akcję zdefiniowaną przez atrybut action. W ten sposób otrzymujemy /example-01/example/HelloWorld. Następnie dodaje sufiks .action do poprzedniej ścieżki URL, a także parametry z URL, jeśli takie istnieją. Powstały plik URL /przykład-01/example/HelloWorld.action?request_locale=en wywoła akcję HelloWorld zdefiniowaną w pliku [struts.xml], przekazując parametr request_locale=en. Ten ostatni nie zostanie przetworzony przez akcję HelloWorld, lecz przez jeden z interceptorów Strutsa, ten, który zarządza internacjonalizacją stron. Parametr request_locale zostanie rozpoznany i przetworzony. Językiem stron stanie się angielski (en).

  • wiersz 19: definiuje link HTML. Atrybut href tagu <a> oczekuje ciągu znaków. W tym przypadku chcemy użyć wartości URL zdefiniowanej w wierszu 16 oraz identyfikatora URL. W tym celu wpisujemy: href="%{URL}". Zmienna URL jest obliczana, a jej wartość przypisywana do atrybutu href. W większości przypadków obliczanie zmiennych odbywa się domyślnie. Na przykład, gdy piszemy
<s:property name= "message "/>

wyświetlana jest wartość właściwości message, a nie ciąg znaków „message”. Jednak w innych przypadkach konieczne jest wymuszenie obliczenia zmiennych lub właściwości. Gdybyśmy napisali href= „URL”, to do atrybutu href zostałby przypisany ciąg znaków URL.

  • wiersze 23–27: tworzą link HTML służący do zmiany języka stron na hiszpański.

2.8. Uruchomienie aplikacji

Uruchamiamy projekt:

  • w [1] uruchamiamy projekt [exemple-01]. Serwer WWW Tomcat uruchamia się wówczas automatycznie, o ile nie był już uruchomiony. Żądanie dotyczące URL, [/exemple-01] i [3] jest wysyłane. Wykorzystywany jest wówczas plik [web.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <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>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>example/HelloWorld.JSP</welcome-file>
    </welcome-file-list>
</web-app>
  • ponieważ w żądanym pliku URL [/exemple-01] nie podano żadnej strony, Tomcat wykorzysta znacznik <welcome_file-list> z wierszy 16 i 18. W związku z tym zostanie wyświetlona strona URL /exemple-01/example/HelloWorld.JSP.
  • Ponieważ Struts 2 przetwarza wszystkie URL (wiersze 8 i 9), ten URL zostanie odfiltrowany przez Struts. Ponieważ nie odpowiada on akcji, ale stronie JSP, to właśnie ta strona zostanie wyświetlona.
  • To, co widzimy w [2], to zatem strona HelloWorld.JSP, którą analizowaliśmy.
  • W [4] widać, że ani tag <title><s:text name="HelloWorld.message"/></title>, ani tag <h2><s:property value="message"/></h2> nie zadziałały. Powodem tego jest fakt, że nie wywołano żadnej akcji. Lista interceptorów uruchamianych przed akcją nie została zatem wykonana, w szczególności ten, który obsługuje internacjonalizację. Znacznik internacjonalizacji <s:text ...> nie mógł zostać poprawnie przetworzony. Ponadto nie istnieje właściwość message, która odwołuje się do pola „message” klasy Action1. Stąd brak wyświetlenia.

Teraz przejdźmy do linku [English]. Otrzymujemy następującą stronę:

  • w [1], czyli żądanym URL. Wyjaśniliśmy już, jak powstaje ten adres URL. Tym razem żądana jest akcja Struts: akcja HelloWorld zdefiniowana w [example.xml].

<struts>
    <package name="example" namespace="/example" extends="struts-default">
        <action name="HelloWorld" class="example.HelloWorld">
            <result>/example/HelloWorld.JSP</result>
        </action>
    </package>
</struts>
  • wykonano metodę execute tej akcji. Widzieliśmy, że zwracała ona klucz success. Z wiersza 4 powyżej wynika, że strona /example/HelloWorld.JSP jest zwracana klientowi w odpowiedzi. Widzimy to w [3].
  • W [1] widzimy, że żądana strona URL jest skonfigurowana za pomocą parametru request_locale=en. Językiem stron będzie odtąd angielski. Wybór języka jest bowiem zapisywany w sesji użytkownika, który zachowa ten wybór do momentu jego zmiany.
  • W tagach [2] i [3] widać, jak działa internacjonalizacja. Tagi <title><s:text name="HelloWorld.message"/></title> oraz <h2><s:property value="message"/></h2> tym razem zadziałały.

Jeśli teraz wybierzemy link [Espanol], otrzymamy stronę w języku hiszpańskim:

2.9. Conclusion

Przeanalizowaliśmy przykład wygenerowany automatycznie przez wtyczkę Struts 2 dla NetBeans. Stwierdziliśmy, że dość trudno jest prześledzić przebieg przetwarzania żądania. W grę wchodzą następujące elementy:

  • konfiguracja [web.xml], [struts.xml]
  • wykonana akcja: interceptory i metoda execute.

Na początku mechanizm działania Struts 2 może wydawać się skomplikowany. Przyzwyczajenie się do niego zajmuje trochę czasu. Teraz przedstawimy serię przykładów, z których każdy wyjaśnia konkretny aspekt Struts.