Od obecného oslavování návrhového vzoru Model-View-Controller z předchozího článku přejdeme k jednoduché pidiaplikaci, na které předvedeme základy webového programování pomocí frameworku Jakarta Struts. Takže předpokládejme, že si chceme připravit jednoduchý formulář pro registraci uživatele do nějakého našeho systému. Jak nám Struts usnadní práci?

Úplné základy

Předpokládám, že už i javoví začátečníci si po předchozím článku pořídili základní propriety nutné pro další pokračování. Pro zopakování – kromě základního minima v podobě Javy samotné (například J2SDK) se jedná o webový kontejner (asi nejznámější Tomcat od Jakarty nebo jednoduchý, rychlý a neméně open source Jetty) a v prvé řadě samotný balík Jakarta Struts v aktuální verzi (nebojte se případné bety, nekouše).

Dále pak předpokládám, že tušíte něco málo o tom, jak taková webová aplikace v Javě vůbec vypadá. Ve zkratce se jedná o adresář nebo jar archív s koncovkou war, a jeho struktura je následující:

aplikace.war
   WEB-INF/ adresář pro konfigurační soubory, není přístupný přes web (proto kromě knihoven a standardně požadovaných souborů je sem vhodné vložit vše, co nepotřebujete explicitně zpřístupnit koncovému uživateli)
      classes/ nezabalené zkompilované třídy, případně properties soubory)
      lib/ knihovny (tj. jar archívy zkompilovaných tříd)
      web.xml standardntí konfigurační soubor aplikace (tzv. web application deployment deskriptor)
      … dále pak jiné adresáře či konfigurační soubory specifické pro konkrétní aplikaci
   *.jsp JSP stránky či statické prvky, které mají být přímo přístupné z webového prohlížeče

Vlastní tvorba aplikací ve Struts se skládá typicky z následujících podúloh:

  1. vytvoření *.properties souboru, v němž budou uloženy kódy přinejmenším chybových zpráv, pro případ potřeby snadné lokalizace naší aplikace sem můžeme dát veškeré statické texty
  2. definice formulářů
  3. vytvoření tříd implementující akce, které se se vykonávají před předáním řízení View vrstvě (typicky se jedná o akce zpracovávání formulářů)
  4. nějaké to JSPčko (nevíte-li, co to je, poučte se u pana Branického) coby šablona, ať víme, jak to vlastně bude vypadat
  5. namapování formulářů, akcí a šablon na sebe a hlavně na vstupní požadavky, jinými slovy též konfigurace komponenty Controller (viz minulé pojednání o MVC)

Teorie už vám jistě leze ušima, přejděme nyní k praxi.

1. Texty

Texty zde můžeme rozumět například chybové zprávy, nadpisy, popisky k menu a vůbec jakékoli statické (tedy ne např. z databáze tahané) informace. Protože používáme klasického Javového mechanismu, i zde můžeme mít více *.properties souborů opatřených vhodnou koncovkou identifikující příslušnou jazykovou variantu, a tím snadno a rychle vytvářet další jazykové verze naší aplikace.

Na lokalizaci se zatím vykašleme a pro jednoduchost v našem základním příkladě použijeme tohoto mechanismu pouze pro definice chybových zpráv.

V naší aplikaci budeme nejspíše potřebovat něco takového:

# když uživatel zapomene povinnou položku
error.missing=musite vyplnit polozku {0}
# uživateli se nepodarilo napsat heslo dvakrát
# stejne za sebou
error.password.mismatch=zadana hesla nesouhlasi
# příliš snadno uhodnutelné heslo
error.password.simple=zvolil jste prilis stupidni heslo
# pokud se nas uzivatel bude snazit osulit tim,
# ze se akci urcenou pro metodu POST pokusi podvrhnout
# parametry prachsprostym GETem (tzv. Cross-site scripting)
error.postOnly=dej ty spinavy pracky pryc, sprostaku!

Čtenář se možná ušklíbne nad tím, co za staromilství mne vede k psaní chybových hlášek bez diakritiky. Staromilství v tom není, jedná se o to, že (jak každý zkušenější javista jistě ví) *.properties soubory musí být psány v kódování iso-8859-1 a znaky specifické pro národní znakové sady se tam mohou dostat pouze v podobě tzv. „unicode escapes“. Ve skutečné aplikaci bychom sice diakritiku nepominuli, ale skutečně použitý properties soubor bychom z našeho zdrojového vytvořili pomocí utility native2ascii.

2. Formuláře

Formuláře reprezentujeme JavaBean třídami, které rozšiřují základní třídu org.apache.struts.action.ActionForm. Typy jejich položek vycházejí z toho, co chceme mít v odpovídajícím formuláři na webové stránce. Je možné používat základních tříd i primitivních typů. Kvůli větší flexibilitě se vřele doporučuje pokud možno všude používat String nebo aspoň se vyhýbat primitivním typům a příslušné konverze provádět až při vyhodnocování formuláře.

Abstraktní třída ActionForm nám předepisuje dvě metody, které můžeme implementovat. Jedná se o metody následující:

public void reset
    (HttpServletRequest request,
     HttpServletResponse response);
# nastavi implicitni hodnoty polozek ve formulari
public ActionErrors validate
    (HttpServletRequest request,
     HttpServletResponse response);
# zde je mozne vlozit kod, ktery ohodnoti smysluplnost zadaneho vstupu do formulare a v pripade potreby vrati chybova hlaseni

Jednoduchý formulář pro registraci tedy můžeme reprezentovat následující fazolí:

package cz.templation.struts.sample1;
import …; // jiste si domyslite…
public class RegistrationForm extends ActionForm {
    private String login = null;
    private String passwd1 = null;
    private String passwd2 = null;
    public String getLogin () {
        return login;
    }
    public void setLogin (String login) {
        this.login = login;
    }
    //… zde si důvtipný čtenář laskavě domyslí
    // get/set metody pro položky passwd1 a passwd2
    public void reset
        (HttpServletRequest request,
         HttpServletResponse response) {
        login = null; passwd1 = null; passwd2 = null;
    }
    public ActionErrors validate
        (ActionMapping mapping,
         HttpServletRequest request) {
        ActionErrors errors = new ActionErrors ();
        if („POST“.compareToIgnoreCase
            (request.getMethod ()) != 0)
        {
            errors.add
                (ActionErrors.GLOBAL_ERROR,
                 new ActionError („error.postOnly“));
            return errors;
        }
        if ((login == null) || login.equals(„“))
            errors.add
                (ActionErrors.GLOBAL_ERROR,
                 new ActionError („error.missing“, „login“));
        }
        // zde si laskavy ctenar domysli dalsi
         // podobne testy
    }
    return errors;
}

Povšimněme si zejména metody validate() a způsobu, jakým se naplňuje instance třídy ActionErrors, která zaznamenává chyby, k nimž v průběhu zpracovávání požadavku došlo. Úvodní test na REQUEST_METHOD je míněn coby ochrana proti cross-site scriptingu.

3. Zpracování formulářů a aplikační logika vůbec

Veškerou vlastní aplikační logiku budeme schovávat do jednotlivých rozšíření abstraktní třídy org.apache.struts.action.Action. V rozumně navržené aplikaci je tato logika zapouzdřena do samostatných tříd a z akcí je pouze volána, těmito nuancemi se ale v našem jednoduchém demonstračním příkladu zabývat nemusíme.

Rozhraní Action nám předepisuje jednu jedinou metodu – execute. Tato metoda je volána v okamžiku, kdy dojde na zpracování požadavku naší akcí. Její parametry nám umožňují přistupovat k aktualnímu HTTP požadavku, neméně aktuální HTTP odpovědi, k odesílaným datům v podobě formulářové tříd a konečně k informacím potřebným ke komunikaci s Controllerem, které jsou schovány v instanci třídy ActionMapping.

V našem příkladě si vystačíme s akcí jedinou. Ta přijme formulář v podobě instance výše vytvořené třídy RegistrationForm (může předpokládat již proběhlé ověření pomocí metody validate) a s daty získanými z formuláře něco udělá. Asi by je měla vložit někam do databáze, ale tím si teď život komplikovat nebudeme a spokojíme se s uložením uživatelem zvoleného přihlašovacího jména k pozdějšímu zobrazení. A taky by koneckonců mohla za určitých okolností způsobit nějaký problémový stav, abychom si mohli demonstrovat, jak Struts řeší zpracovávání výjimek.

A pokud vše dopadne dobře, vrátíme kouzelný navigační objekt, do nějž zabalíme nějakou symbolickou hodnotu, třeba konvenční „success“.

package cz.templation.struts.sample1;
import …; // viz předchozí příklad
public class RegistrationAction extends Action {
    public ActionMapping execute (…)
        throws RandomException {
        if (new Random().nextFloat() < 0.25)
            throw new RandomException („buliky buliky“);
        // do scope request ulozime login name z formulare
        request.setAttribute
            („loginName“,
             ((RegistrationForm).form).getLogin ());
        // a timhle dame najevo, ze vse dopadlo dobre
        return mapping.findForward(„success“);
    }
}

Samozřejmě předpokládáme existenci nějaké té cz.templation.struts.sample1.RandomException, kterou si bystrý čtenář domyslí a ten méně bystrý dohledá v balíku s naším příkladem (pozor, celý archiv má takřka 1MB).

4. Šablony

V našem příkladu budeme potřebovat dvě základní šablony. V jedné zobrazíme formulář a tu druhou uživateli ukážeme, pokud se mu podařilo formulář úspěšně odeslat. Pokud uživatel spáchal nějakou chybu, předpokládejme, že mu v další kapitolce nějak podvrhneme opět původní formulář.

Jako šablony můžeme použít prachobyčejné JSP stránky. Struts nám k nim dodávají několik více či méně užitečných tagů. Některým, zejména <html:form> a <html:messages>, se nevyhneme ani v našem minimálním příkladu, protože zajišťují spolupráci s výše popsanými komponentami. Další užitečné značky se nacházejí v následujících taglibech:

  • bean – různé nástroje pro práci s JavaBeans, zjednodušují práci oproti defaultním JSP tagům a kromě toho umožňují tagem <bean:write> zobrazovat lokalizované verze textových informací
  • logic – tagy implementující jednoduché příkazy jako podmínka či cyklus, místo nich je možno použít silnějších a standardnějších nástrojů, které nám přinesl standard JSTL
  • template a tiles – usnadní vytváření znovupoužitelných šablon v rámci projektu

index.jsp

Úvodní stránka s registračním formulářem:

<%@ page language=“java“
    contentType=“text/html; charset=iso-8859-2″ %>
<%@ taglib uri=“/WEB-INF/tlds/struts-html.tld“
    prefix=“html“ %>
<%@ taglib uri=“/WEB-INF/tlds/struts-bean.tld“
    prefix=“bean“ %>
<html><body>
<h1>Registrace</h1>
<html:messages id=“msg“ header=“errors.header“
    footer=“errors.footer“>
    <bean:write name=“msg“ /><br />
</html:messages>
<html:form action=“/register“>
    login: <html:text property=“login“ />&ltbr />
    heslo: <html:password property=“passwd1″ />&ltbr />
    heslo pro kontrolu ještě jednou:
        <html:password property=“passwd2″ />&ltbr />
    <html:submit value=“odeslat“ />
</html:form>
</body></html>

V šabloně používáme značky z taglibů html a bean, proto úvodní deklarace. Značka <html:form> se nám ve výsledku promění v obyčejný HTML element <form>, jeho parametr <action> udává název akce z konfiguračního souboru struts-config.xml, ke kterému se dostaneme níže. V odpovídající html formulářové značky se změní tagy <html:text> a <html:password>, jejichž použití nám zaručí, že při návratu na formulářovou stránku v případě chyby budou automaticky inicializovány uživatelem vyplněnými hodnotami.

Případné chybové zprávy nám zobrazí tag <html:messages>, jeho atributy header a footer udávají textíky, kterými by naše chybové zprávy měly být obklopeny. Tento tag prochází všechny chybové zprávy, které se mají zobrazit. Zobrazovaná zpráva je uvnitř tagu zpřístupněna pod jménem daným atributem id, k jejímu zobrazování používáme značky <bean:write>.

ok.jsp

Druhá šablona tvoří stránku, na níž uživatele chceme poslat v případě úspěšné registrace:

<%@ page language=“java“
    contentType=“text/html; charset=iso-8859-2″ %>
<%@ taglib uri=“/WEB-INF/tlds/struts-bean.tld“
    prefix=“bean“ %>
<html><body>
<h1>Registrace</h1>
<p>Bravo.
Nyní bychom Vám v ostré aplikaci sdělili, že se Vám podařilo
úspěšně se zaregistrovat pod uživatelským jménem
„<bean:write name=“loginName“ />“.
</p>
</body></html>

Značku <bean:write> tentokrát používáme ke zobrazení uživatelova přihlašovacího jména, které jsme si v akci RegisterAction uložili právě pod názvem "loginName"… vzpomínáte?

5. Jak to drží dohromady?

Spolupráci všech dosud vytvořených komponent zajišťuje implementace Controlleru, kterou je třída org.apache.struts.action.ActionServlet. Této třídě je nutno předhodit konfigurační soubor, v němž definujeme závislosti mezi formuláři, akcemi, šablonami a požadavky, určíme zdroj textových popisků a volitelně pak spoustu dalších věcí, které přesahují rámec tohoto článku.

Ještě než se pustíme do samotného konfigurování, musíme náš Controller nějak zpřístupnit, což uděláme pomocí standardních prostředků v deployment deskriptoru web.xml např. následujícím způsobem:

<?xml version=“1.0″ encoding=“ISO-8859-1″?>
<!DOCTYPE web-app
    PUBLIC „-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN“
    „http://java.sun.com/dtd/web-app_2_3.dtd“>
    <!– Konfigurace servletu ActionServlet –>
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>
            org.apache.struts.action.ActionServlet
        </servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>
                /WEB-INF/struts-config.xml
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!– Namapování servletu na požadavky –>
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <!– a dalsi –>
</web-app>

Značkou <servlet-mapping> říkáme, že každý požadavek na zdroj končící příponou „.do“ chceme poslat na ActionServlet.

Jistě jste si všimli, že se v konfiguračních parametrech třídy ActionServlet (ano, to je ten náš Controller) odkazujeme mimojiné na nějaký konfigurační soubor jménem struts-config.xml. A tím našemu Controlleru sdělíme, co od něj vlastně v naší aplikaci očekáváme:

<?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>
  <!– ========== Definice formularu =============== –>
  <form-beans>
    <form-bean name=“registrationForm“
      type=“cz.templation.struts.sample1.RegistrationForm“ />
  </form-beans>
  <!– ========== Mapování akcí ==================== –>
  <action-mappings>
    <action path=“/register“
      type=“cz.templation.struts.sample1.RegistrationAction“
      name=“registrationForm“
      input=“/index.jsp“
      scope=“request“>
      <exception key=“error.random“
        type=“cz.templation.struts.sample1.RandomException“
        path=“/index.jsp“ />
      <forward name=“success“ path=“/ok.jsp“ />
    </action>
  </action-mappings>
  <!– ========== Zdroje textíků ================== –>
  <message-resources parameter=“ApplicationResources“ />
</struts-config>

Tedy řekli jsme, že budeme používat jeden formulář reprezentovaný třídou RegistrationForm, který si označíme názvem "registrationForm". Dále pak budeme disponovat jednou akcí, která zpracovává právě tento formulář. Pokud tato akce vrátí "success", necháme zobrazit stránku ok.jsp. Při problémech s validací formuláře se vrací na stránku "index.jsp" (definovanou parametrem input). Tato akce může vyhodit výjimku "RandomException", při které se rovněž vrátíme na stránku index.jsp a zobrazíme chybovou hlášku odpovídající položce "error.random" z výše zmíněného properties souboru. A když už jsme u toho, jedná se konkrétně o soubor ApplicationResources.properties (bez názvu balíku, takže ho směle můžeme – ba i musíme – umístit přímo do adresáře WEB-INF/classes).

Vážně to funguje?

No přece bych vám nelhal. Náš formulářík si můžete vyzkoušet on-line, případně si stáhnout kompletní zdrojový kód včetně antovského skriptu.

Co dál?

Struts samozřejmě nabízí mnohem více, než jsme si ukázali v tomto základním příkladu. Můžeme si například nadefinovat pool spojení k databázi, můžeme nakonfigurovat validaci vstupů z formuláře, která kromě ověřování na serveru bude generovat i javascriptový kód, můžeme si ušetřit spoustu práce na „View“ vrstvě pomocí využití šablonovacích taglibů, v tomto článku jsme už nastínili možnost snadno a rychle lokalizovatelných aplikací atd., atd. Některé z těchto postupů budou popsány v dalších článcích. Zatím na shledanou a těším se na komentáře v diskusi pod článkem.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět