Struts, komentovaný příklad: základ aplikace

22. 12. 2003
Doba čtení: 15 minut

Sdílet

Struts je framework pro snadnější tvorbu a správu rozsáhlejších webovských aplikací napsaných v Javě. Vychází z principu MVC (Model-View-Controller), mocného to zaklínadla. První proniknutí do jeho struktury však může být pro začínající tvůrce poněkud těžkopádné. A právě těm je určen tento právě vznikající krátký seriál.

Nebudeme dopodrobna pronikat do nejjemnějších zákoutí tohoto prostředí. Rovnou si ukážeme, jak vystavět jednoduchou aplikaci založenou na Struts s minimálním úsilím – základ získáme z archivu struts-blank.war.

Jsem si dobře vědom toho, že na internetu lze najít velké množství ukázkových příkladů. Často ale bývají nedostatečně komentované nebo považují spoustu věcí za samozřejmost, proto se jimi zabývají jen okrajově. Já sám se považuji, co se týče webových aplikací, spíše za začátečníka. Zde předložená aplikace je jedna z mála, které jsem pomocí Struts sám vytvořil. Přesto, nebo spíše právě proto věřím, že tak mohu postihnout věci, které mně samotnému připadaly zvláštní nebo mi působily potíže a (snad ne až tak vzácný) čtenář sám posoudí, bude-li mu článek přínosem.

Pojďme tedy na to. Rovnou se vyhněme úvodním popisům MVC, javy a jejích možností pro webové programy. Tyto informace si snadno zjistíte sami, začít můžete některými odkazy na konci tohoto článku. Uvedu jen několik věcí, které budete potřebovat, než můžeme pokračovat:

  • Javu včetně vývojového prostředí – např. originální J2SDK od Sunu,
  • popisovaný Struts framework, vyvíjený v rámci Apache Software Foundation,
  • jeden z mnoha dostupných javovských kontejnerů, jako Tomcat, Jetty nebo JBoss,
  • Ant, neboli obdobu klasické make utility, používanou javovskými vývojáři.

Tohle všechno musíte spojit dohromady. Část popisující, jak toho dosáhnout, také přeskočíme, neboť je též obsahem mnoha jiných článků. Řekněme jen, že většinou stačí rozbalit stažený archiv a správně nastavit některé systémové proměnné (JAVA_HOME, CLASSPATH, ANT_HOME, JBOSS_HOME, případně další).

Problémeček k řešení

Následujícím příkladem jsem se nechal inspirovat u jednoho kamaráda (říkejme mu třeba Pecok), který jej dostal zadaný v rámci cvičení z programování na nejmenované vysoké škole (ZČU – malá neplacená reklama v rámci podpory vysokého školství). Tady je:

Na vstupu dostanete dvě celá čísla A a B. Vaším úkolem je nalézt jiná dvě čísla, jejichž součet odpovídá prvnímu zadanému číslu A, rozdíl pak druhé číslu, tedy B. 

Hmm, kdo se nudí, může zkusit sám. Pro ostatní a hlavně pro studenty oné zmíněné univerzity uvedu jedno z možných řešení:

Vytvoříme adresář numbers, reprezentující knihovní balíček, a v něm soubor CalculateBean.java:

package numbers;

public class CalculateBean {
  private

int sum = 0;
  private
int diff = 0;

  public
CalculateBean(int sum, int diff) {
    this.sum = sum;
    this.diff = diff;
  }

  public
ResultBean solve() {
    float first = (sum + diff) / 2.0f;
    float second = sum - first;
    return new ResultBean(first, second);
  }
} 

jež problém řeší a výsledek vrací v objektu popsaném v souboru ResultBean.java:

package numbers;

public class ResultBean {
  private float n1;
  private float n2;

  public
ResultBean(float n1, float n2) {
    this.n1 = n1;
    this.n2 = n2;
  }

  public float getN1() { return n1; }
  public float getN2() { return n2; }
} 

Takto jsme, aniž bychom se nějak zvlášť snažili, splnili ono M z MVC modlitby. Tedy model, někdy též vznešeně nazývaný Business Logic. Pravda, zde je model velice jednoduchý, stále však tvoří základ aplikace. Nyní bychom rádi také V (view – prezentační část). Můžeme vytvořit jednoduchou konzolovou aplikaci, grafickou komponentu (založenou například na knihovně swing), nebo webovou aplikaci dostupnou odkudkoli.

Struts-blank.war

Součástí Strutsů je i jednoduchá aplikace struts-blank.war (najdete ji v adresáři webapps), která nám má usnadnit vývoj. Jde v podstatě o prázdnou aplikaci (zobrazuje jednu úvodní obrazovku) se základní konfigurací. Z této aplikace vyjdeme.

Archiv struts-blank.war rozbalte (můžete použít libovolný archivátor pro .zip soubory). Z adresářové struktury rovnou smažte adresáře META-INF a pages, nebudeme je potřebovat – alespoň ne v naší ukázkové aplikaci. Do adresáře WEB-INF/src/java překopírujte celý dříve vytvořený adresář numbers. Tímto způsobem jsme získali předpřipravený controller (naše C).

Budu předpokládat, že jste alespoň částečně seznámeni se strukturou Strutsů. Podrobné vysvětlování by bylo příliš zdlouhavé, takže se zaměřím přímo na konkrétní kroky, které je potřeba udělat, aby bylo možné aplikaci přeložit a spustit.

V adresáři WEB-INF je několik .tld a .xml souborů. Později se k nim vrátíme. Přejděme do podadresáře src. Zde je uložen předpřipravený soubor build.xml, který můžeme použít k snadnému kompilování a sestavování aplikace pomocí Antu (dá se říct, že za nás odvede mravenčí práci).

První důležitou řádkou je v něm proměnná servlet.jar, kterou musíte správně nastavit. Archiv servlet.jar nebo podobný by měl obsahovat vámi použitý javovský kontejner. Bez tohoto archivu nepůjdou některé části kódu zkompilovat. Další důležitou proměnnou je dispath.project, která nastavuje místo, kam se bude ukládat aplikace sestavená do webového archivu (.war, případně .jar). Také byste měli nastavit project.title aproject.distname. Druhá zmíněná proměnná nastavuje název archivu, ke kterému se přidá koncovka war. U sebe jsem použil toto nastavení:

<property name="servlet.jar"
    value="jboss/server/default/lib/javax.servlet.jar"/>
<property name="distpath.project" value="prog/proj/struts"/>

<-- Project settings -->
<property name="project.title" value="Struts-root.cz"/>
<property name="project.distname" value="cisla"/> 

Nyní by se vám už mělo podařit aplikaci zkompilovat a sestavit. Z aktuálního adresáře (WEB-INF/src) vaší aplikace zavolejte ant compile dist. Zdrojové .java soubory z balíčku numbers by se měly přeložit (objeví se ve WEB-INF/classes) a celé se to sestavit do aplikace – v mém případěcisla.war v adresáři …/struts.

Malá vsuvka …

… pro přiblížení se čtenáři: teď už si fakt musím jít lehnout. Přece jenom je čtvrt na tři v noci. Vypadá to, že první díl bude trošku delší, ale prostě ho není kde rozdělit. Další se snad urodí kapánek kratší.

Základ vrstvy view

Ták. Nějakých deset hodin jsem si odpočinul (rozuměj pospal) a můžeme pokračovat. Začneme tím, že zprovozníme základní zobrazovací stránku. Nazvěme ji třeba calculate.jsp. Prozatím pouze chceme, aby zobrazila krátké uvítání, později přidáme formulář pro zadávání vstupních čísel, zobrazování výsledků, případně hlášení chyb.

Napišme si tedy jednoduchou calculate.jsp stránku, která zobrazí pouze nadpis:

<%@ page language="java" contentType="text/html; charset=iso-8859-2" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>

<html:html locale="true">
<head>
  <title>Čísla-root>/title>
</head>

<body bgcolor="white">
  <h4>Výpočet skrytých čísel</h4>
</body>
</html:html> 

Uložte ji do kořenového adresáře vaší aplikace, tedy tam, kde už je soubor index.jsp. Nyní se do souboru index.jsp podívejme. Jedinou jeho funkcí je, že přesměrovává prohlížeč někam jinam. Změňme řádek provádějící toto přesměrování na

<logic:redirect forward="calculate"/>

V dalším kroku se ještě musíme podívat na soubor struts-config.xml (je uložen v adresáři WEB-INF). To je soubor, který se stará o nastavení controller vrstvy. Jinými slovy říká, která akce se kdy provede, nahrává správné třídy pro zpracování formuláře a podobné legrace. Při zpracování předchozího řádku se controller dozví, že má provést přesměrování na stránku označenou jako calculate – jakýsi alias. V souboru struts-config.xml teď musíme řídící vrstvě říct, s čím je tento alias spojen.

Najděte v něm sekci global-forwards a změňte ji na

<global-forwards>
  <forward
    name="calculate"
    path="/calculate.jsp"/>
</global-forwards>

A je to. Nyní můžete aplikaci znovu sestavit (příkazem ant dist), přesunout na správné místo vašeho kontejneru (např. jboss/server/de­fault/deploy) a zobrazit v prohlížeči. Jak přesně na to, se dozvíte z dokumentace vámi použitého kontejneru. U mne se nachází na adrese

http://localhost:8080/cisla

V souboru web.xml je nastaveno, že pokud neuvedete konkrétní stránku, použije se index.jsp. Ta, ve spolupráci s řídící vrstvou, zařídí přesměrování na calculate.jsp.

Lokalizace

Struts lze snadno využít k lokalizaci aplikace do více jazyků. Je to opět již předpřipraveno, tak proč toho nevyužít. Změňte soubor calculate.jsp na

<%@ page language="java" contentType="text/html; charset=iso-8859-2" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>

<html:html locale="true">
<head>
  <title><bean:message key="calculate.title"/></title>
</head>

<body bgcolor="white">
  <h4><bean:message key="calculate.title"/></h4>
</body>
</html:html> 

Jedinou změnou je to, že jsme místo vpisování zpráv přímo do souboru použili tag bean:message, který na dané místo vloží zprávu z definičního souboru application.pro­perties nebo podobného. Těch může být i víc, pro každý jazyk jeden. Strukturou a fungováním tohoto souboru se prozatím nebudeme zabývat, ještě se k němu vrátíme. Abychom při vývoji nemuseli editovat příliš mnoho souborů, můžeme použít jeden trik, který nám struts nabízí.

Podívejme se opět do souboru struts-config.xml a v definicimes­sage.resources přidejme parametr null=„false“. Pokud bychom to neudělali a v souboru application.pro­perties neměli definovány všechny zprávy, skončila by kompilace jsp stránky s chybou. V tomto případě se použije chybějící zpráva ohraničená otazníky, tedy např. ???cs_CZ.calcu­late.title???. V kterékoli fázi vývoje pak můžeme požadované zprávy dopsat.

Požadujeme data, čili formulář

Přidat do stránky formulář může být pro začínající uživatelé strutsů poněkud frustrující záležitost. Časem vám to však přejde do krve a zjistíte, že je to vlastně jednoduché a logické.

Nejprve vytvoříme formulář v jsp stránce calculate.jsp:

<%@ page language="java" contentType="text/html; charset=iso-8859-2" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>

<html:html locale="true">
<head>
  <title><bean:message key="calculate.title"/></title>
</head>

<body bgcolor="white">
  <h4><bean:message key="calculate.title"/></h4>

  <html:form action="solve.do">
    <bean:message key="calculate.sum"/>
    <html:text name="NumbersForm" property="n1" /><br/>
    <bean:message key="calculate.diff"/>
    <html:text name="NumbersForm" property="n2" /><br/>
    <html:submit>
      <bean:message key="calculate.submit"/>
    </html:submit>
  </html:form>

</body>
</html:html> 

Přidali jsme dvě políčka pro zadání dvou čísel. Důležité jsou tyto položky:

  • U definice formuláře nám atribut action=„solve.do“ říká, že po odeslání formuláře bude vyvolána akce solve. Koncovka .do je definována v souboru web.xml a podle ní řídící vrstva pozná, kam má předat řízení.
  • U definice textových polí formuláře máme dva důležité parametry. name udává symbolické pojmenování javovské třídy obsahující popis tohoto formuláře (definuje se opět ve struts-config.xml souboru – v tomto případě NumbersForm). Parametr property říká, která vlastnost dané (java-bean) třídy se k tomuto poli váže. V tomto případě budeme potřebovat dvě vlastnosti n1, n2 a k nim příslušné get a set metody.

Formulář máme a můžeme přistoupit sestavení již zmíněné java-bean třídy. Soubor nazvěme pro změnu NumbersForm.java, opět v balíčku numbers:

package numbers;

import org.apache.struts.action.ActionForm;

public class NumbersForm extends ActionForm {
  private String
n1;
  private String
n2;

  public String
getN1() { return

n1; }
  public String
getN2() { return
n2; }

  public void setN1(String
number) {
this.n1 = number; }
  public void setN2(String
number) {
this.n2 = number; }
} 

Třída popisující formulář je velice jednoduchá. Musí být potomkem třídy org.apache.strut­s.action.Acti­onForm a dodržovat standardy java-bean specifikace, tzn. kupříkladu zde využité soukromé vlastnosti a k nim odpovídající veřejné metody pro čtení a nastavení těchto vlastností.

To by tedy bylo. Nyní ještě toto vše potřebujeme začlenit do logiky strutsů. Ano, hádáte správně, budeme opět editovat soubor struts-config.xml. Nejdříve musíme přidat náš formulář, tedy spojit symbolické jméno s konkrétní třídou. To se provádí v sekciform-beans:

<form-beans>
  <form-bean
    name="NumbersForm"
    type="numbers.NumbersForm"/>
</form-beans>

Druhým krokem je spojit formulář s akcí na jeho zpracování. Tím se budeme zabývat v další části, takže zde si pomůžeme standardní předdefinovanou akcí, která sice s formulářem nic neprovádí, ale umožní nám zkontrolovat, zda jsme předchozí kroky provedli správně.

Najděme sekci action-mappings a změňme ji na následující kód. Jednotlivé parametry říkají:

  • path – controller hledá shodu s akcí bez koncovky .do. Jelikož jsme volali akci solve.do, akce bude pojmenována /solve (úvodní lomítko je povinné).
  • type – je konkrétní třída dané akce. Zde jsme využili jednu ze standardních definovaných akcí, která provádí pouze přesměrování na jinou stránku.
  • name – symbolické jméno formuláře, který se k této akci váže, pokud takový existuje.
  • scope – ve které uživatelské relaci se má třída popisující formulář vyhledat (resp. vytvořit). Také jsme mohli použít session.
  • parameter – takto můžeme do třídy předat nějaký parametr, v tomto případě udávající, kam uživatele přesměrovat.
<action-mappings>
  <action
    path="/solve"
    type="org.apache.struts.actions.ForwardAction"
    name="NumbersForm"
    scope="request"
    parameter="/calculate.jsp"/>
</action-mappings>

Teď by tedy mělo být možno zkompilovat novou třídu NumbersForm.java (ant compile), sestavit (ant dist) a po nahrání do kontejneru i spustit. Pravda, formulář tam je, zapsat do něj můžete, co chcete, ale jinak se nic neděje.

Chceme výsledek, čili akce

Fajn. Takže s tím něco uděláme. Potřebujeme napsat vlastní akční třídu, která do programu zapojí už dříve vytvořenou aplikační logiku a zpřístupní výsledek. Pojďme na to.

Vytvořit akční třídu je opět relativně jednoduché, pokud už máte s programováním servletů nějaké zkušenosti. Akční třída musí být potomkem org.apache.strut­s.action.Acti­on a nejspíš chcete překrýt minimálně metodu execute(). Ta vykoná nějaký kód – nejlépe by měla zavolat aplikační logiku a výsledek předat zobrazovací vrstvě nebo jiné akci – a vrátí instanci třídy ActionForward.

Prohlédněme si kód akce. Nejprve vezme náš formulář a z něj vytáhne potřebná data – obě zadaná čísla. Ta předá aplikační logice a vrácený výsledek (třídu ResultBean) uloží do relace požadavku. Mohli bychom použít i session, ale není nutné uchovávat výsledek po celou dobu, co je uživatel k aplikaci připojen.

Nakonec podle zadaného parametru vyhledá a předá řízení dále. Jméno calculate bylo již dříve definováno ve struts-config.xml souboru v sekci global-forwards a bude tedy opět zobrazena stránka s formulářem.

package numbers;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.struts.action.*;

public class NumbersAction extends Action
{
  public
ActionForward execute(ActionMapping       mapping,
ActionForm          form,
              HttpServletRequest 
request,
              HttpServletResponse
response)
  {
    NumbersForm numbers =
(NumbersForm)
form;

      String n1 =

numbers.getN1();
      String n2 =
numbers.getN2();
      CalculateBean calculator = new
CalculateBean(Integer.parseInt(n1), Integer.parseInt(n2));
      ResultBean result = calculator.solve();
      request.setAttribute("resultBean", result);

    return
mapping.findForward("calculate");
  }
} 

Aby bylo možné tuto třídu přeložit, musí byt dostupný už zmiňovaný archiv servlet.jar. Pokud jej nemáte nastaven v systémové proměnné CLASSPATH, můžete pro překlad využít našeho mravence. V konfiguračním skriptu build.xml je sekce pro nastavení cest k potřebným balíkům – <path id=„compile.clas­spath“>. Do této sekce přidejte řádek

<pathelement path="${servlet.jar}"/>

Proměnnou servlet.jar jsme nastavili na začátku tohoto seriálu. Nyní by kompilace měla proběhnout bez problému. Abychom mohli výsledek našeho snažení ověřit, musíme provést dva poslední zásahy – obligátní poeditování souboru struts-config.xml a vytvoření kódu pro zobrazení výsledku. První část je snadná.

Při tvorbě formuláře jsme připravili popis akce (sekce action-mappings). Nyní pouze místo dříve použité standardní akce využijeme naši.

<action-mappings>
  <action
    path="/solve"
    type="numbers.NumbersAction"
    name="NumbersForm"
    scope="request"/>
</action-mappings>

Dokonce se to trošku zjednodušilo, že ano. Nakonec přidáme část kódu v souboru calculate.jsp pro zobrazení výsledku.

<%@ page language="java" contentType="text/html; charset=iso-8859-2" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>

<html:html locale="true">
<head>
  <title><bean:message key="calculate.title"/></title>
</head>

<body bgcolor="white">
  <h4><bean:message key="calculate.title"/></h4>

  <logic:present name="resultBean">
    <bean:message key="calculate.number1"/>
    <bean:write name="resultBean" property="n1" /><br/>
    <bean:message key="calculate.number2"/>
    <bean:write name="resultBean" property="n2" />
  </logic:present>
  <br/>
  <html:form action="solve.do">
    <bean:message key="calculate.sum"/>
    <html:text name="NumbersForm" property="n1" /><br/>
    <bean:message key="calculate.diff"/>
    <html:text name="NumbersForm" property="n2" /><br/>
    <html:submit>
      <bean:message key="calculate.submit"/>
    </html:submit>
  </html:form>
</body>
</html:html> 

Všimněte si, že jsme použili i tag <logic:present>. Má to ten účel, že pokud není výsledek k dispozici (např. jste na stránku přišli poprvé a nezadali jste žádná data ke zpracování), tato část se nepoužije.

Po opětovném zkompilování, sestavení a redeployi (tj. znovunačtení aplikace v kontejneru) již všechno funguje. Ovšem doporučuji do formuláře zadávat opravdu jenom celá čísla, která jsou očekávána. Jinak aplikace zbouchne, nejspíše s chybou NumberFormatEx­ception.

A to je vše?

Ano. Protentokrát. Myslím, že toho bylo až dost. Webovou aplikaci jsme vystavěli opravdu s minimálním úsilím – pár řádek kódu, něco málo změn v konfiguračních souborech. A za tu cenu jsme získali aplikaci dostupnou takřka odkudkoli, lehce přeložitelnou do více jazyků (lidských samozřejmě), včetně skriptu, usnadňujícího nejen kompilování a sestavování.

ict ve školství 24

Aplikace je to sice naprosto bezbranná, ale v příštím dílu jí nasadíme mithrilovou kazajku v podobě ověřování vstupních dat, a to jak na straně serveru, tak i na straně klienta. Opět se stejně minimálním úsilím, neboť spousta toho je už k dispozici, i když to možná ještě nevidíme.

Odkazy:

java.sun.com – tady je java doma
jakarta.apache­.org/struts – ono výše zmiňované
ant.apache.org – mraveniště
www.jboss.org – oblíbený free java aplication server
Struts na root.cz – pěkný seriál Lukáše Zapletala
Struts na Interval.cz – velmi čtivý seriál od Pavla Kolesnikova
Struts na linuxzone.cz – přehledový článek Vojtěcha Patrného
Programujeme Jakarta Struts – dokonce vyšla kniha v češtině, no ne