Struts
Servlety reprezentują programy Javy, działające po stronie serwera WWW. Umożliwiają generowanie dynamicznych stron WWW. Mają następujące zadania:
Pisanie HTMLa w olbrzymiej liczbie println()
bardzo szybko stało się męczące.
Odpowiedzią na ten problem są Java Server Pages. Pliki JSP to dokumenty tekstowe podobne do plików HTMLowych.
Oczywiście pozwalają one zamieścić kod Javy w pliku. JSP pozwala wymieszać statycznie napisany
HTML z dynamicznie wygenerowanym przez servlet. Właściwie cały pomysł polega na umieszczeniu
kodu w dokumencie HTML, co jest przeciwnością wcześniejszych servletów, gdzie HTML był zaszyty w kod Javy.
Dlatego mówi się, że powstanie JSP wywróciło pisanie servletów na drugą stronę.
Java Beans to nic innego jak klasy, trzymające się konwencji nazewniczej zdefiniowanej przez Suna. Konwencja ta dotyczy nazw metod, atrybutów i sposobu dostępu do nich.
Logika biznesowa jest sercem aplikacji. To ona wykonuje operacje na danych. Wszystkie czynności i procesy mają tutaj swoją podstawę.
<html><head><title>Important title</title></head>
<body>
<someScript>
dbConnection = openDBConnection(someDB)
resultSet = dbConnection.executeQuery('select bookName from books')
loop over resultSet{
print (resultSet.field('bookName') + '<br>')
}
</someScript>
</body></html>
Można również w ten sposób pracować używając JSP i servletów.
A teraz wyobraźmy sobie, że mamy 70 takich dialogów i chcemy dodać w tabeli books
dodać nowe pole,
mówiące, że książka została usunięta. Pewnie czeka nas mnóstwo roboty.
Co byśmy zyskali separując logikę od interfejsu:
Aby kontrolować interakcje między interfejsem a logiką potrzebna jest centralna jednostka. Przez taki byt przechodziłyby wszystkie ważne procesy, niezależnie od tego która część logiki lub która część interfejsu byłaby w proces zaangażowana.
Jeśli implementując aplikację rezygnujemy z centralnego sterowania i łączymy interfejs bezpośrednio z logiką narażamy się na niedogodności:
Co zyskujemy wykorzystując centralne sterowanie:
W Struts ten schemat wygląda tak:
Dodatkowo mamy możliwość użycia bibliotek rozszerzających technologię, której używamy. Nie musimy używać JSP, możemy użyć Tilesów lub JavaFaces. Beansy możemy rozszerzyć na przykład o Hibernate'a lub EJB.
Jeśli piszemy aplikację składającą się z kilku stron, to projektowanie jej zgodnie z paradygmatem MVC nie jest niezbędne (chociaż ładne). Możemy wtedy sięgać do logiki biznesowej prosto z interfejsu, w razie modyfikacji logiki, przedefiniować cały interfejs i nie martwić się o poprawność przesyłanych między interfejsem a logiką danych. Wcześniej czy później pewnie uda nam się doprowadzić aplikację do działania. Natomiast kiedy mamy dużą aplikację mającą kilkadziesiąt dynamicznie generowanych stron, rozbudowaną logikę i bazę danych Controller staje się niezbędny i może oszczędzić olbrzymie ilości czasu.
org.apache.struts.action.ActionServlet
i mapowanie wszystkich URL kończących się na .do
do naszego servletu. Końcówka to mapowanie bibliotek
z tagami na odpowiednie plikiaction-mappings
, zacznijmy od najprostszego:
<action path="/Welcome" forward="/pages/Welcome.jsp"/>Jest to mapowanie akcji "/Welcome" na odpowiedni forward. Czyli wywołując akcję zostaniemy przekierowani do strony /pages/Welcome.jsp
<action path="/Welcome" type="org.apache.struts.webapp.example.WelcomeAction"> <forward name="failure" path="/Error.jsp" /> <forward name="success" path="/Welcome.jsp" /> </action>Teraz jeśli proces zakończy się porażką, nasza Akcja będzie mogła wywołać zamapowane tutaj przekierowania z obiektu mapping za pomocą metod
findForward("success")
i findForward("failure")
.
Dzięki temu nie musimy zaszywać na stałe żadnych URI.
scope
. JSP definiują następujące poziomy:
MyCart mycart = new MyCart(...); request.setAttribute("cart", mycart);Bean jest widoczny w stronie, do której się przekierujemy. Możemy go użyć na przykład tak:
<jsp:useBean id="cart" scope="request" class="com.mycompany.MyApp.MyCart"/>
Action
. Aby przetwarzać żądanie zgodnie z własnymi upodobaniami
należy przedefiniować metodę execute(...):
public final class WelcomeAction extends Action {
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
...
}
Mapujemy klasy w pliku struts-config.xml w węźle action-mappings
, najważniejsze atrybuty
to:
- path: Kontektsowa ścieżka do akcji.
- type: Pełna Javowa nazwa klasy.
- name: Nazwa elementu <form-bean> używanego przez akcje.
- validate: Jeżeli metoda
validate
ma zostać wywołana przed
metodą execute
ustawiamy wartość atrybutu na "true".
- unknown: Jeżeli akcja ma być domyślna dla tej aplikacji. Czyli wywołanie akcji, której nie
obsługuje żadna klasy spowoduje wywołanie metody z tej klasy
- forward: Mapowanie następnych przejść.
ActionForms
Dla zapewnienie łączności między między interfejsem a modelem (dokładnie w tym
kierunku) używamy klas rozszerzających ActionForms
. Są to beany
tworzone i wypełniane automatycznie, gdy użytkownik wysyła formularze. Są one
przechowywane w otoczeniu sesji (domyślnie) lub żądania. Jeśli przechowujemy
je w sesji należy koniecznie pamiętać o przedefiniowaniu metody reset()
.
Pisząc ActionForm
należy pamiętać o:
- Nie wymagają one żadnych szczególnych metod. Dzięki temu łatwo zidentyfikować
ich rolę w systemie. Zwykle mają tylko metody getXxx i setXxx dla każdego atrybutu
i nie posiadają żadnych metod logiki biznesowej.
- Są zaopatrzone w mechanizm walidacji. Jeśli przedefiniujemy metodę
validate
Struts wywoła ją automatycznie w czasie tworzenia. W wypadku, gdy poprawność zależy od więcej
niż jednego beana, należy walidację umieścić w obiekcie klasy Action
.
- Należy umieścić odpowiedni atrybut dla każdego pola w formularzu, na podstawie
którego bean będzie tworzony.
- Przyciski i inne kontrolki mogę zostać również definiowane jako atrybuty. Pozwala to
na przykład ustalić, który przycisk został wciśnięty.
Najważniejsze atrybuty <form-bean> w struct-config.xml to:
- name: Niepowtarzalna nazwa identyfikująca beana.
- type: Pełna nazwa Javowej klasy beana.
ExceptionHandlers
Można zdefiniować własne obiekty obsługujące wyjątki wyrzucane przez obiekty Action
.
Należy zacząć od napisania podklasy org.apache.struts.action.ExceptionHandler
i przedefiniować
metodę execute
. Metoda ta powinna obsłużyć wyjątek i zwrócić przekierowanie (ActionForward
),
aby Struts wiedział gdzie ma się udać. Następnie zamapować obsługę wyjątków w struts-config.xml, na przykład tak:
<global-exceptions>
<exception
key="some.key"
type="java.io.IOException"
handler="com.yourcorp.ExceptionHandler"/>
</global-exceptions>
Zaawansowane mapowanie
Uniwersalne mapowania
Najłatwiej wytłumaczyć to na przykładzie. Oto zastosowanie wzorców do mapowania wszystkich
akcji zaczynających się od /edit:
<action
path="/edit*"
type="org.apache.struts.webapp.example.Edit{1}Action"
name="{1}Form"
scope="request"
validate="false">
<forward
name="failure"
path="/mainMenu.jsp"/>
<forward
name="success"
path="/{1}.jsp"/>
</action>
"*" pozwala na zamapowanie wszystkich URI typu /editUser
ale już nie /editUser/add
.
Do ciągu znaków dopasowanego do "*" możemy się odwoływać przez {1}. Generalnie wzorce mamy takie:
- * - dowolny ciąg znaków (może być zerowej długości) nie zawierający "/".
- ** - dowolny ciąg znaków (może być zerowej długości) mogący zawierać "/".
- \znak - dopasowuje się do podanego znaku (\* - "*", \\ - "\"), przydaje się przy znakach o specjalnym znaczeniu.
W mapowaniach do wartości dopasowanych do części wzorca możemy się odwoływać przez {n} (n=1..9), cały URI jest dostępny
pod {0}.
Struts - rozszerzenia
Validator
Zamiast przesyłania do danych z formularzy do akcji i walidowania ich w kontrolerze mamy możliwość użycia Validatora, który
sprawdzi poprawność danych zanim zostaną one wysłane. Jeśli nie są one zgodne z zadanymi wytycznymi, to akcja nie zostanie
podjęta. Plugin validatora musimy zadeklarować w pliku struts-config.xml.
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validator/validation.xml" />
</plug-in>
We właściwościach "pathnames" podajemy ścieżki do plików określających sposób walidacji. Reguły wyglądają mniej więcej tak:
<field property="age" depends="required,integer,intRange">
<arg0 key="employee.age"/>
<arg1 name="intRange" key="${var:min}" resource="false"/>
<arg2 name="intRange" key="${var:max}" resource="false"/>
<var><var-name>min</var-name><var-value>18</var-value></var>
<var><var-name>max</var-name><var-value>65</var-value></var>
</field>
Warto obejrzeć plik validation.xml z przykładu. Atrybuty depends są zdefiniowane
w pliku validator-rules.xml.
Biblioteka tagów Bean.
Tagi Bean służą do operacji na beanach, definiowania nowych w dowolnym zasięgu, zmiany wartości pól w beanie, pobierania wartości z beanów oraz tworzenia zmiennych dowolnego typu dostępnych później w skryptletach i innych tagach. Funkcjonalność części tego tagliba jest podobna do obecnej w standardzie JSP, jednak w wielu obszarach ją rozszerza.
Większość tagów przyjmuje atrybut "id", który określa nazwę tworzonej zmiennej. Pozostałe atrybuty mogą być stosowane zamiennie, "name" oznacza źródłowy bean, "property" jego pole, a "value" wartość którą chcemy przypisać (czyli wyklucza się on z dwoma poprzednimi). Przykład:
<bean:define id="imie" name="osoba" property="imie" scope="request" toScope="session"/>
Oznacza umieszczenie w sesji pod kluczem "imie" pola "imie" z beana "osoba" znajdującego się w requeście.
Kolejny przykład powoduje pobranie ciastka "klient" i umieszczenie go pod zmienną cust:
<bean:cookie id="cust" name="klient"/>
Za pomocą opisywanej biblioteki można wypisywać na wyjście JSPa.
<bean:write name="cust" property="adresy[2].ulica" scope="session"/>
<bean:message key="message.hello" arg0='<%= user.getFullName() %>'/>
Logic
Kolejnym ciekawym taglibem dostępnym ze Strutsami jest Logic.
Ma ona wiele cech wspólnych z JSTLem, zawiera tagi zachowujące się jak instrukcje warunkowe w językach programowania oraz pętle.
<logic:tagWarunkowy value="name" cookie="client">
Klient zalogowany.
</logic:tagWarunkowy>
Tagi dla warunków to np. present, equal, lessThan, match.
<%
java.util.List lista = new java.util.ArrayList();
lista.add(new Integer(12));
lista.add(new Integer(5));
%>
<logic:iterate id="element" collection="<%= lista %>">
Mamy tu dostęp do zmiennej element
</logic:iterate>
<logic:iterate id="element" name="kolekcja">
Mamy tu dostęp do zmiennej element
</logic:iterate>
W pierwszym przypadku atrybutowi "collection" przypisujemy kolekcję ze zmiennej na stronie, a w drugim tag sam będzie szukać w różnych zasięgach obiektu o nazwie "kolekcja".
Message resources
Plik propertiesowy definiowany w struts-config.xml'u w tagu
<message-resources parameter="nazwa.properties"/>
Zakładając, że ma on postać:
file.request=Plik do wysłania
button.ok=Wyślij
button.cancel=Anuluj
Można z niego korzystać następująco:
<table class="form">
<tr><td><bean:message key="file.request"/></td></tr>
<tr><td><html:file property="plik"/></td></tr>
[...]
<html:submit><bean:message key="button.ok"/></html:submit>
<html:cancel><bean:message key="button.cancel"/></html:cancel>
Konsekwętnie unikając pisania jakiegokolwiek tekstu w plikach JSP a korzystając z opisywanego mechanizmu
łatwo wprowadzać nowe wersje językowe, wystarczy przetłumaczyć jeden plik.
Tiles
<logic:notPresent name="USER">
<logic:forward name="login" />
</logic:notPresent>
<tiles:insert page="/common/layout.jsp">
<tiles:put name="title" value="Strona główna" />
<tiles:put name="menu" value="/common/menu.jsp" />
<tiles:put name="body" value="/mainPageBody.jsp" />
</tiles:insert>
Plik layout może wyglądać następująco:
<html lang = "pl">
<body>
<table width="100%" border="0">
<tr>
<td class="logo" width="15%">Logo </td>
<td class="header"> <tiles:getAsString name="title" /> </td>
</tr>
<tr>
<td class="menu"> <tiles:insert attribute="menu" /> </td>
<td class="body"> <tiles:insert attribute="body" /> </td>
</tr>
</table>
</body>
</html>
Powyższy przykład dobrze prezentuje zastosowanie tagów logic oraz tiles.
Pierwszy plik zawiera zbiera kawałki strony znajdujące się w różnych JSPach, na przykład
element z menu jest wspólny dla wszystkich stron, może być zatem umieszczony w osobnym pliku.
Drugi plik zawiera zebranie wszystkich części w całość.
Dyna Action Form
Pewnym ułatwieniem przy tworzeniu formularzy i akcji są DynaFormBeany - tworzone na podstawie zapisu w deskryptorze,
istniejące jedynie w pamięci typy beanów formularzy.
W struts-config.xml'u definicja takiego elementu wygląda następująco:
<form-bean name="addrEditForm"
type="org.apache.struts.action.DynaActionForm">
<form-property name="addr_name" type="java.lang.String"/>
<form-property name="company_name" type="java.lang.String"/>
<form-property name="nip" type="java.lang.String"/>
<form-property name="first_name" type="java.lang.String"/>
<form-property name="last_name" type="java.lang.String"/>
<form-property name="street" type="java.lang.String"/>
<form-property name="home" type="java.lang.String"/>
<form-property name="flat" type="java.lang.String"/>
<form-property name="city" type="java.lang.String"/>
<form-property name="code" type="java.lang.String"/>
<form-property name="country" type="java.lang.String"/>
<form-property name="phone" type="java.lang.String"/>
<form-property name="panstwo" type="org.apache.struts.util.ImageButtonBean"/>
<form-property name="dalej" type="org.apache.struts.util.ImageButtonBean"/>
</form-bean>
W JSP nie ma żadnej różnicy w używaniu zdefiniowanego w pełni ręcznie FormBeana od DynaFormBeana. W akcji natomiast
nie można pobrać bezpośrednio pola z beana (bo nie ma takiego), zatem trzeba skorzystać z klasy PropertyUtil:
ImageButtonBean dalej = (ImageButtonBean) PropertyUtils.getSimpleProperty(form, "dalej");
String first_name = (String) PropertyUtils.getSimpleProperty(form, "first_name");
Pluginy do Eclipsa
Wtyczkami do Eclipsa, które są godne polecenia i ułatwiają pracę ze Strutsami sa:
- MyEclipse Płatna ale bardzo
dobra, ciągle rozwijana, obsługuje wiele technologii J2EE.
- Struts Console Pozwala
wizualnie edytować struts-config.xml'a, wystarczająco aktualna aby nadawała się do używania.
- Improve Struts Trochę mniej
świeża i stabilna od poprzedniej, również umożliwia wizualną edycję deskryptora.
Pozostałe, które miałek okazję testować były niezbyt stabilne albo wręcz nieużyteczne.
Źródła
Autorzy: Maciek Wiśniewski i Piotrek Witusowski