A więc do dwóch warstw naszej aplikacji teraz podejmę próbę dopracowania trzeciej - umożliwiającej dostęp do aplikacji przy pomocy przeglądarki WWW. Jako że ostatnio przechodzę okres zauroczenia frameworkiem JSF, wybór technologii realizacji zadania nie był trudny... a wiec do rzeczy...
Konfiguracja serwletu FacesServlet i webowego kontekstu aplikacjiFramework JSF jest oparty na ogólnie znanym wzorcu projektowym MVC (Model View Controller ) nic więc dziwnego, że najważniejszym elementem jego konfiguracji jest właściwe zdefiniowanie parametrów Serwletu Rozdzielającego (Servlet Dispatcher) . W przypadku JSF nosi on nazwę FacesServlet. Jego konfiguracja odbywa się w deskryptorze rozmieszczenia - pliku
/WEB-INF/web.xml.
Sama modyfikacja pliku jest bardzo prosta sprowadza się ona do kilku rzeczy:
Po pierwsze należy zdefiniować sam serwlet. Robi się to przy pomocy następującego kodu:
<servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet>Ponieważ nasza aplikacja ma mieć możliwość korzystania z obiektów stworzonych przez Springa należy zdefiniować jej kontekst springa, przez definicję odpowiedniego listenera:
<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>Ostatnim krokiem będzie zdefiniowanie filtra, który wszystkie żądania zakończone przyrostkiem *.faces będzie przekierowywał do serwletu Faces Servlet. Wygląda on następująco:
<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping>Nasz serwlet jest już gotowy do pracy... teraz czas na stworzenie pierwszego komponentu JSF.
Pierwszy scenariusz - wyświetlenie listy kategoriiNa początek chciałbym wyświetlić listę kategorii produktów, które są w bazie. Chciałbym, aby była ona prezentowana w postaci tabelki, z której to możnaby przejść do edycji kategorii, usunąć ją, dodać nową, a także obejrzeć jakie produkty do niej należą.
Wykorzystanie technologii JSF implikuje, że do realizacji tego scenariusza będziemy potrzebowali definicji dwóch rzeczy, a mianowicie:
- strony na której zdefiniowany będzie UI kompotent odpowiedzialny za renderowanie tabelki
- beana ( backing beana), który w architekturze MVC umiejscowiony będzie w warstwie modelu i stanowić będzie zaplecze dla komponentu UI. Jego zadaniem będzie dostarczenie danych potrzebnych do wypełnienia tabelki, jak również metod stanowiących o logice działania (więcej o beanach napisałem w artykule JSF- czyli jak ja to rozumiem...).
Nasz pierwszy backing bean - CategoryListUI Pierwszym krokiem, który należy wykonać jest zdefiniowanie obiektu - beana, który zasili naszą tabelkę w dane i dostarczy obsługującej ją logiki. W terminologii JSF bean taki nazywa się "
backing bean".
A więc umiejscawiam go w pakiecie
mw.toystore.web.ui i nadaję nazw
CategoryListUI.
Nasz bean będzie korzystał z kontekstu springowego i za jego pośrednictwem uzyska referencje do interfejsu biznesowego
IProductManager. Aby obiekt ten został prawidłowo zaincjalizowany przy pomocy mechanizmu IoC w klasie
backing beana należy zdefiniować własność tego typu, oraz metody dostępowe (setter i getter). Stosowny fragment kodu wygląda następująco:
.......private IProductManager productManager; public IProductManager getProductManager() { return productManager; } public void setProductManager(IProductManager productManager) { this.productManager = productManager; }.......Teraz, gdy nasz komponent potrafi już sięgnąć do warstwy biznesowej można stworzyć metodę, za której pośrednictwem komponent interfejsu użytkownika (UI) leżący na stronie i skojarzony z naszym beanem uzyska dostęp do listy obiektów kategorii. Niech nazywa się ona
getCategoryList(). Jej konstrukcja będzie bardzo prosta, bo ograniczy się tylko do wywołania metody z obiektu interfejsu biznesowego i zwrócenia na zewnątrz pobranej z niższych warstw aplikacji kolekcji obiektów kategorii. Nasza metoda wygląda następująco:
public List getCategories(){ return this.productManager.getCategoryList(); }W tym momencie nasz bean potrafi zaserwować listę kategorii, posiada więc minimalną funkcjonalność. Dzięki niemu komponent interfejsu użytkownika będzie w stanie uzyskać kolekcję obiektów reprezentujących różne kategorie.
Zanim jakikolwiek komponent interfejsu użytkownika skorzysta z naszego beana musi zostać wykonana stosowna konfiguracja, która dokonuje się przez dokonanie modyfikacji w pliku
/WEB-INF/faces-config.xml. Przyjrzyjmy się im trochę dokładniej.
Propagowanie beana jako Managed BeanPrzyszedł czas na integrację naszego beana z frameworkiem JSF. Odbywa się ona przez dokonanie odpowiednich wpisów w pliku konfiguracyjnym
/WEB-INF/faces-config.xml.
Myślę, że można powiedzieć, że każdy backing bean, którego opiszemy w tym pliku nabywa kilka nowych cech. Przede wszystkim, nadana mu tutaj logiczna nazwa jest widoczna dla wszystkich komponentów interfejsu użytkownika. Mogą się do niego odwoływać i na podstawie uzyskanych od niego informacji renderować odpowiednie elementy interfejsu użytkownika.
Cyklem życia takiego beana zarządza już sam framework. W żadnym miejscu nie ma konieczności martwienia się o jego stworzenie i poprawną inicjalizację. Zadba o to framework. Być może dlatego po propagacji, beany uzyskują nową nazwę - managed beans.
Ale po kolei. Przyjrzyjmy się kolejnym elementom naszego pliku /WEB-INF/faces-config.xml.
Cała konfiguracja objęta jest tagami <faces-config> </faces-config>.
Przed definicją beana znajduje się wpis konfiguracyjny o specjalnym znaczeniu. Mianowicie powoduje on, że framework JSF uzyskuje dostęp także do kontekstu springowego (temat integracji frameworków Spring i JSF opisałem w artykule "Próba połączenia JSF i Spring..." ). Dzięki temu do naszego managed beana możliwe jest wstrzyknięcie obiektów zarządzanych przez kontener IoC (a więc np. naszego interfejsu biznesowego IProductManager). Stosowny fragment pliku wygląda następująco:
<application>
<variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
</application>
Definicja samego managed beana wygląda następująco:
<managed-bean>
<managed-bean-name>categoryListUI</managed-bean-name>
<managed-bean-class>mw.toystore.web.ui.CategoryListUI</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>productManager</property-name>
<value>#{productManager}</value>
</managed-property>
</managed-bean>
Powyższe wpisy powodują że stworzony zostanie managed bean o identyfikatorze
categoryListUI (pod tą nazwą będzie on widzialny dla komponentów UI). Jako klasę beana wskazałem omawianą wcześniej klasę mw.toystore.web.ui.CategoryListUI . Kolejna właściwość określa, że bean będzie przesyłany w obiekcie żądania.
Na uwagę zasługuje sekcja:
<managed-property>
<property-name>productManager</property-name>
<value>#{productManager}</value>
</managed-property>
Jak pamiętamy, klasa naszego beana ma zdefiniowaną właściwość o nazwie propertyManager, oraz dwie metody dostępowe do niej. W tym momencie zostają one wykorzystane przez framework do ich inicjalizacji. W kontekście Springa odszukany zostaje obiekt o logicznej nazwie productManager ( id="productManager"), a następnie jego wartość zostaje wstrzyknięta do naszego beana za pomocą wywołania metody setProductManager().
Nasz bean został spropagowany, jest dostępny dla stron JSP, teraz możemy przejść do tworzenia strony JSP, na której umieszczony zostanie komponent interfejsu użytkownika, który skorzysta z danych zwróconych z naszego beana i zrenderuje dla nas stosowną dla nich tabelę.
Strona z komponentem UI - categoryList.jspNa początek w katalogu głównym naszej aplikacji zakładam plik cateogoryList.jsp. Chciałbym aby komponent JSF umieszczony na niej wykorzystywał zdefiniowany wcześniej managed bean i wyświetlał nazwy wszystkich kategorii. A więc potrzebna będzie tabela.
Z początku nie wiedziałem jak się zabrać za jej tworzenie ale z pomocą przyszło google i
artykuł Jacka Laskowskiego poświęcony tabelom w JSF. Znalazłem tam sczególowy opis znacznika
<h:dataTable>Aby uniknąć problemów z wyświetlaniem polskich znaczków rozpoczynam od ustawienia kodowania na utf-8:
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>Na początek definicja bibliotek znaczników związanych z wykorzystaniem elementów JSF:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>Ponieważ będzie ona potrzebna na wszystkich stronach wykorzystujących komponenty JSF, wydzieliłem ją do pliku
/WEB-INF/jsp/include.jsp i włączam do moich stron za pomocą dyrektywy include:
<%@ include file="/WEB-INF/jsp/include.jsp"%>Od tej chwili do elementów JSF będę mógł odwołać się przy pomocy schematu "
przedrostek_tagliba:nazwa_elementu", gdzie jako przedrostek wystąpi jeden z dwóch zdefiniowanych powyżej (
f dla tagów z jsf-core i h dla tagów z jsf-html).
A więc od początku. Na szczycie hierarchii elementów JSF umieszczonych na stronie zawsze musi znaleźć się element
f:view>/f:view>. Dopiero w jego wnętrzu mogą zostać umieszczone inne tagi. Definicja naszej najprostszej tabelki będzie wyglądała następująco:
<h:dataTable value="#{categoryListUI.categories}" var="category" border="1" styleClass="listtab"> <h:column> <f:facet name="header"> <f:verbatim>Name</f:verbatim> </f:facet> <h:outputText value="#{category.name}" /> </h:column> </h:dataTable>Kluczowe znaczenie mają dwa atrybuty ustawiane na samym początku a mianowicie:
- value - powoduje że lista obiektów, które mają zostać zaprezentowane w tabelce pobrana zostanie z obiektu o nazwie catgoryListUI (nazwa logiczna z pliku faces-config.xml), na podstawie jego własności categories . Tu należałoby wyjaśnić jedną rzecz. Mianowicie bean ten nie musi mieć własności categories, wystarczy, że tak jak w naszym przypadku będzie miał zdefiniowaną metodę getCategories().
- var - jest to nazwa logiczna obiektu pobranego z kolekcji przy pojedynczej iteracji
Atrybut "
styleClass" powoduje że zrenderowana tabelka w kodzie wynikowym przypisana będzie do klasy CSS o nazwie "
listtab" (klasę tą zdefiniujemy później, przy itegracji z
SiteMeshem).
Definicja pojedynczej kolumny tabeli następuje we wnętrzu tagu
<h:column> </h:column>. Nie musimy oprogramowywać samej iteracji - wystarczy definicja nagłówka kolumny (
<f:facet name="header"></f:facet name="header">) i wskazania pola z którego przy kolejnej iteracji powinna zostać wyciągnięta wartość wierszowa (
<h:outputText value="#{category.name}" />).
A więc definicja naszej najprostszej tabeli wygląda na kompletną. Czas na uruchomienie serwera , a potem umieszczeniem na nim naszej aplikacji.
Po wpisaniu adresu:
http://localhost:3333/ToyStoreJSFApp/categoryList.facesNa ekranie pojawiła się tabelka:
A więc wygląda na to że pierwszy i kluczowy przypadek użycia naszej aplikacji został zrealizowany. Wyświetla ona tabelkę z kategoriami produktów.