r18-05(1), Informacje dot. kompa


W niniejszym rozdziale:

Rozdział 18.

JavaServer Pages

JavaServer Pages, znana powszechnie jako JSP, to technologia utworzona przez Sun Microsystems, blisko związana z serwletami. JSP była jedną z pierwszych prób utworzenia ogólno dostępnego systemu tworzenia zawartości opartego na serwletach. Osoby od dawna zajmujące się serwletami mogą pamiętać, że JSP została przedstawiona po raz pierwszy na wiosnę 1998 — na tyle wcześnie, by pierwsze wydanie niniejszej książki mogło zawierać krótki samouczek testowej wersji 0.91 JSP. Oczywiście JSP zmieniła się bardzo od tego czasu. W tym rozdziale opisana zostanie JSP 1.1, opierająca się na Servlet API 2.2.

Podobnie jak w przypadku serwletów, Sun udostępnia specyfikację JSP (utworzoną przez grupę ekspertów składającą się z niezależnych producentów i osób), po czym inni producenci konkurują w swoich implementacjach tego standardu. Różnica pomiędzy JSP i innymi technologiami opartymi bezpośrednio na serwletach polega na tym, że JSP jest specyfikacją, nie produktem i wymaga wsparcia ze strony serwera. Większość producentów kontenerów serwletów zapewnia tę obsługę, między innymi Tomcat, w którym mechanizm JSP nosi nazwę Jasper. JSP jest podstawowym składnikiem Java 2, Enterprise Edition (J2EE).

Jednym z głoszonych celów JSP (cytat ze specyfikacji) jest „umożliwienie oddzielenia zawartości dynamicznej i statycznej”. Innym celem jest „umożliwienie tworzenia stron WWW, które zawierają elementy dynamiczne w łatwy sposób, ale z maksymalną mocą i elastycznością”. JSP spełnia dobrze oba te zadania. Jednak, zgodnie z kwestią „maksymalnej mocy” przy tworzeniu JSP, autor strony JSP zawsze posiada całkowitą kontrolę nad systemem, i w związku z tym można powiedzieć, że JSP umożliwia oddzielenie zawartości od grafiki, ale nie wymusza jej ani nie narzuca, jak to się dzieje w przypadku jej alternatyw. Jest to zjawisko podobne do tego, że C++ umożliwia projektowanie obiektowe, ale nie promuje tego doskonałego sposobu tak mocno, jak to się dzieje w przypadku Javy.

JSP jest również technologią bardzo elastyczną, i istnieje wiele sposobów jej wykorzystania. Jednym z nich jest „skrótowy” sposób tworzenia serwletów — programista serwletów, dobrze znający Javę, może umieścić kod Javy bezpośrednio na stronie JSP, zamiast pisać kompletny serwlet — co daje ułatwienia takie jak wyeliminowanie wywołań wyj.println(), umożliwienie bezpośredniego wskazywania na pliki JSP i wykorzystanie własności autokompilacji JSP, Jednak poprzez tworzenie strony JSP zamiast serwletu programista traci pełny kontakt z kodem oraz możliwość kontroli prawdziwego środowiska uruchomieniowego (np. rozszerzanie com.oreilly.servlet.CacheHttpServlet lub tworzenie wyniku binarnego (obrazka)). Z tego powodu najlepiej jest pozostawić prawdziwe kodowanie zwykłym serwletom, a strony JSP wykorzystać przede wszystkim do tworzenia logiki prezentacyjnej.

Nawet podczas zastosowania stron JSP jedynie do logiki prezentacyjnej, można je wykorzystać na wiele sposobów. Jednym z nich jest użycie komponentów JavaBeans osadzonych na stronie. Innym tworzenie własnych znaczników wyglądających jak HTML, ale będących tak naprawdę punktami zaczepienia do wspomagającego kodu Javy. Jeszcze innym jest wysyłanie wszystkich żądań do serwletu, który wykonuje logikę biznesową, po czym przesyła żądanie do JSP tworzącej stronę przy pomocy mechanizmu RequestDispatcher. Technika ta jest często nazywana architekturą Model 2, która to nazwa pochodzi od specyfikacji JSP 0.92. Istnieją również inne mechanizmy noszące dziwne nazwy takie jak Model 1½, Model 2½, czy Model 2+1. Niemożliwe jest wskazanie najlepszej techniki stosowania JSP.

W niniejszym rozdziale opisane zostaną różne sposoby wykorzystania JSP, począwszy od „skrótu do serwletu” a skończywszy na własnych znacznikach. Opis będzie krótki, ale powinien dostarczyć podstaw do porównania JSP z jej alternatywami. Nie zostaną opisane wszystkie opcje architektury, a zamiast tego nacisk zostanie położony na szczegóły techniczne. Opcje architektury i większa ilość informacji na temat JavaServer Pages jest dostępna na stronie głównej JSP pod adresem http://java.sun.com/products/jsp („ściąga” składni jest dostępna pod adresem http://java.sun.com/products/jsp/syntax.pdf) oraz w książce „JavaServer Pages” autorstwa Hansa Bergstena (O'Reilly).

Wykorzystywanie JavaServer Pages

Najbardziej podstawową funkcją JSP jest umożliwienie bezpośredniego umieszczenia kodu serwletu w statycznym pliku HTML. Każdy blok kodu serwletu (nazywany skryptletem) jest ograniczany otwierającym znacznikiem <% i zamykającym %>. Wykorzystywanie skryptletu ułatwia kilka predefiniowanych zmiennych. Sześć najpopularniejszych to:

HttpServletRequest request

Żądanie serwletu.

HttpServletResponse response

Odpowiedź serwletu.

javax.servlet.jsp.JspWriter out

Urządzenie wyświetlające, stosowane podobnie jak PrintWriter, posiadające jednak inną charakterystykę buforowania.

HttpSession session

Sesja użytkownika.

ServletContext application

Aplikacja WWW.

javax.servlet.jsp.PageContext pageContext

Obiekt wykorzystywany przede wszystkim do rozdzielania implementacji serwera, ale często wykorzystywany bezpośrednio do współdzielenia zmiennych pomiędzy stronami JSP i wspomagającymi elementami i znacznikami.

Można dostrzec, że klasy JSP umieszczone są w pakiecie javax.servlet.jsp.

Przykład 18.1 przedstawia prostą stronę JSP wyświetlającą spersonalizowane „Witaj” przy pomocy predefiniowanych zmiennych request i out. Jeżeli posiada się serwer obsługujący JavaServer Pages i pragnie się przetestować tę stronę, należy umieścić plik w podkatalogu katalogu macierzystego dokumentów serwera i zapamiętać ją z rozszerzeniem .jsp. Zakładając, że strona została zapamiętana jako witaj1.jsp, dostęp do niej można uzyskać pod URL-em http://serwer:port/witaj1.jsp lub, jeżeli plik zostanie umieszczony w ścieżce kontekstowej jsp, URL będzie wynosił http://serwer:port/jsp/witaj1.jsp.

Przykład 18.1.

Powitanie przy pomocy JSP

<HTML>

<HEAD><TITLE>Witaj</TITLE></HEAD>

<BODY>

<H1>

<%

if (request.getParameter("nazwa") == null) {

out.println("Witaj świecie");

}

else {

out.println("Witaj, " + request.getParameter("nazwa"));

}

%>

</H1>

</BODY></HTML>

Przykładowy wynik uruchomienia powyższego programu przedstawiony jest na rysunku 18.1.

Rysunek 18.1.

Powitanie przy pomocy JavaServer Pages

0x01 graphic

Zasady działania

Jak działa JSP? Poza obszarem widoczności serwer automatycznie tworzy, kompiluje, ładuje i uruchamia specjalny serwlet tworzący zawartość strony, jak przedstawiono na rysunku 18.2. Można myśleć o tym specjalnym serwlecie jak o serwlecie roboczym, działającym w tle. Statyczne części strony HTML są tworzone przez serwlet roboczy przy pomocy ekwiwalentów wywołań wyj.println(), podczas gdy części dynamiczne są dołączane bezpośrednio. Na przykład, serwlet przedstawiony w przykładzie 18.2 mógłby być serwletem roboczym dla witaj1.jsp działającym na serwerze Tomcat.

0x01 graphic

Rysunek 18.2.

Generowanie stron JavaServer Pages

Przykład 18.2.

Generowany automatycznie serwlet roboczy dla witaj1.jsp

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

import javax.servlet.jsp.tagext.*;

import java.beans.*;

import java.io.*;

import java.util.*;

import org.apache.jasper.runtime.*;

import org.apache.jasper.*;

public class _0002fwitaj_00031_0002ejspwitaj1_jsp_0 extends HttpJspBase {

static {

}

public _0002fwitaj_00031_0002ejspwitaj1_jsp_0( ) {

}

private static boolean _jspx_inited = false;

public final void _jspx_init() throws JasperException {

}

public void _jspService(HttpServletRequest request, HttpServletResponse response)

throws IOException, ServletException {

JspFactory _jspxFactory = null;

PageContext pageContext = null;

HttpSession session = null;

ServletContext application = null;

ServletConfig config = null;

JspWriter out = null;

Object page = this;

String _value = null;

try {

if (_jspx_inited == false) {

_jspx_init();

_jspx_inited = true;

}

_jspxFactory = JspFactory.getDefaultFactory();

response.setContentType("text/html;charset=ISO-8859-1");

pageContext = _jspxFactory.getPageContext(this, request, response,

"", true, 8192, true);

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

// HTML // begin [file="C:\\ witaj1.jsp";from=(0,0);to=(4,0)]

out.write("<HTML>\r\n<HEAD><TITLE>Witaj</TITLE></HEAD>\r\n<BODY>\r\n<H1>\r\n");

// end

// begin [file="C:\\ witaj1.jsp";from=(4,2);to=(11,0)]

if (request.getParameter("nazwa") == null) {

out.println("Witaj œwiecie");

}

else {

out.println("Witaj, " + request.getParameter("nazwa"));

}

// end

// HTML // begin [file="C:\\ witaj1.jsp";from=(11,2);to=(14,0)]

out.write("\r\n</H1>\r\n</BODY></HTML>\r\n");

// end

} catch (Exception ex) {

if (out != null && out.getBufferSize() != 0)

out.clearBuffer();

if (pageContext != null) pageContext.handlePageException(ex);

} finally {

if (out != null) out.flush();

if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);

}

}

}

Kiedy wyświetla się stronę JSP po raz pierwszy można zauważyć, że jej wywołanie zabiera pewną ilość czasu. Jest to czas potrzebny serwerowi na stworzenie i skompilowanie serwletu roboczego, co zostało nazwane karą pierwszej osoby. Następne żądania powinny następować ze zwykłą szybkością, ponieważ serwlet może ponownie wykorzystać serwlet w pamięci. Jedynym wyjątkiem jest zmiana pliku .jsp, co serwer rozpoznaje podczas wykonywania następnego żądania tej strony, po czym rekompiluje serwlet działający w tle. Jeżeli kiedykolwiek wystąpi błąd kompilacji, można spodziewać się, że serwer zakomunikuje go w pewien sposób, zazwyczaj poprzez stronę zwracaną klientowi i/lub dziennik zdarzeń serwera.

Wyrażenia i deklaracje

Oprócz skryptletów, JavaServer Pages pozwala na umieszczanie kodu na stronie przy pomocy wyrażeń i deklaracji. Wyrażenie JSP rozpoczyna się znacznikiem <%=, a kończy %>. Każde wyrażenie Javy pomiędzy tymi dwoma znacznikami jest oceniane, jego wynik konwertowany do łańcucha String, a tekst dołączany bezpośrednio do strony. Technika ta eliminuje nieporządek wywołania wyj.println(). Na przykład, <%= buu => dołącza wartość zmiennej buu.

Deklaracja rozpoczyna się od <%!, a kończy na %>. Pomiędzy tymi znacznikami można umieścić dowolny kod serwletu, który powinien pozostać poza metodą usługową serwletu. Zadeklarować można zmienne statyczne lub egzemplarzowe, albo też zdefiniować nowe metody. Przykład 18-3 przedstawia stronę JSP, która wykorzystuje deklarację do zdefiniowania metody pobierzNazwa() oraz wyrażenie ją wyświetlające. Komentarz na początku pliku pokazuje, że komentarze JSP oznaczane są znacznikami <%-- i --%>.

Przykład 18.3.

Powitanie przy pomocy deklaracji JSP

<%-- witaj2.jsp --%>

<HTML>

<HEAD><TITLE>Witaj</TITLE></HEAD>

<BODY>

<H1>

Witaj, <%= pobierzNazwa(request) %>

</H1>

</BODY>

</HTML>

<%!

private static final String DOMYSLNA_NAZWA = "świecie";

private String pobierzNazwa(HttpServletRequest zad) {

String nazwa = zad.getParameter("nazwa");

if (nazwa == null)

return DOMYSLNA_NAZWA;

else

return nazwa;

}

%>

Powyższy kod JSP zachowuje się identycznie jak witaj1.jsp.

Specjalne metody jspInit() i jspDestroy() mogą zostać zaimplementowane wewnątrz deklaracji. Są one wywoływane przez metody init() i destroy() serwletu działającego w tle i dają stronie JSP możliwość zadeklarowania kodu, który powinien zostać wykonany podczas inicjacji i niszczenia. Strona JSP nie może omijać standardowych metod init() i destroy(), ponieważ metody te są ostatecznie deklarowane przez serwlet roboczy.

Instrukcje

Instrukcja JSP pozwala stronie JSP na kontrolowanie pewnych aspektów jej serwletu roboczego. Instrukcje mogą zostać wykorzystane do nakazania serwletowi roboczemu ustawienia jego typu zawartości, importowania pakietu, kontroli buforowania wyświetlania i zarządzania sesją, rozszerzania różnych superklas oraz specjalnej obsługi błędów. Instrukcja może nawet określać wykorzystanie języka skryptowego innego niż Java.

Składnia instrukcji musi zawierać jej nazwę oraz parę nazwa-wartość atrybutu, a całość musi być zwarta pomiędzy znacznikami <%@ i %>. Cudzysłowy zamykające wartość atrybutu są obowiązkowe:

<%@ nazwaInstrukcji nazwaAtrybutu="wartoscAtrybutu" %>

Instrukcja page pozwala JSP na kontrolowanie generowanego serwletu poprzez ustawienie specjalnych atrybutów wymienionych poniżej:

contentType

Określa typ zawartości generowanej strony. Na przykład:

<%@ page contentType="text/plain" %>

Domyślnym typem zawartości jest text/plain; charset=8859_1.

import

Określa listę klas i pakietów, które tworzony serwlet powinien importować. Większa ilość klas może zostać określona w formie listy rozdzielonej przecinkami. Na przykład:

<%@ page import="java.io.*,java.util.Hashtable" %>

Domyślna, ukryta i zawsze dostępna lista klas to java.lang.*,javax.servlet.*,javax.servlet.http.*,javax.servlet.jsp.*.

buffer

Określa minimalną wymaganą wielkość bufora odpowiedzi w kilobajtach, podobnie do metody serwletu setBufferSize(). Wartość powinna zostać zapisana w formie ##kb. Specjalna wartość none wskazuje, że zawartość powinna zostać przekazana bezpośrednio do odpowiedniego PrintWriter w ServletResponse (który może, ale nie musi przekazać zawartość klientowi). Na przykład:

<%@ page buffer="12kb" %>

Domyślna wartość wynosi 8kb.

autoFlush

Określa, czy bufor powinien być opróżniany, kiedy jest pełny, czy zamiast tego powinien zostać wywołany wyjątek IOException. Wartość true wskazuje na opróżnianie, false na wyjątek. Na przykład:

<%@ page autoFlush="true" %>

Wartość domyślna wynosi true.

session

Wskazuje, że strona pragnie posiadać dostęp do sesji użytkownika. Wartość true umieszcza w zakresie zmienną session i może ustawić cookie klienta zarządzające sesją. Na przykład:

<%@ page session="false" %>

Wartość domyślna wynosi true.

errorPage

Określa stronę, którą należy wyświetlić, kiedy na stronie wystąpi Throwable, które nie zostanie przechwycone przed dotarciem do serwera. Okazuje się to przydatne, ponieważ nie jest trudno wykonać operacje try i catch na blokach podczas tworzenia stron JSP. Na przykład:

<%@ page errorPage="blad.jsp" %>

Domyślne zachowanie zależy od implementacji. Ścieżka jest względna wobec kontekstu, tak więc nie trzeba się przejmować dodawaniem właściwej ścieżki kontekstu. Celem może być JSP, ale nie jest to konieczne. Jeżeli cel jest serwletem, może on pobrać Throwable jako atrybut kontekstu javax.servlet.jsp.jspException.

isErrorPage

Wskazuje, że strona została zaprojektowana jako cel errorPage. Jeżeli wartość wynosi true, strona może uzyskać dostęp do ukrytej zmiennej o nazwie exception w celu odczytania Throwable.

language

Określa język skryptowy wykorzystany w częściach strony zawierających kod. Zastosowany język musi na tyle dobrze współpracować z Javą, aby uwidocznić potrzebne obiekty Javy środowisku skryptu. Na przykład:

<%@ page language="javascript" %>

Domyślna wartość wynosi java, jest to jedyny język rekomendowany przez specyfikację.

Wykorzystanie instrukcji

Przykład 18.4 przedstawia stronę JSP noszącą nazwę bladRob.jsp, która wykorzystuje kilka instrukcji. Po pierwsze ustawia atrybut session dyrektywy page na false, ponieważ strona nie wykorzystuje obiektu sesji i w związku z tym nie istnieje potrzeba, by serwer tworzył sesję. Następnie ustawia atrybut errorPage na /bladBierz.jsp, tak więc jeżeli na stronie wydarzy się niewyłapany wyjątek, strona bladBierz.jsp będzie obsługiwać wyświetlanie komunikatu o błędzie. Główna część strony jest prosta. Wyzwala ona wyjątek w celu wywołania zachowania errorPage. (Powód wykorzystania sprawdzenia if zostanie opisany później.)

Przykład 18.4.

Zły chłopiec

<%-- bladRob.jsp --%>

<%@ page session="false" %> <%-- Wyłączenie niepotrzebnych cookies --%>

<%@ page errorPage="/bladBierz.jsp" %> <%-- Ogólna strona obsługująca błędy --%>

<%-- A teraz wywołanie błędu --%>

<%

if (System.currentTimeMillis() > 0) {

throw new Exception("uups");

}

%>

Strona bladBierz.jsp jest przedstawiona w przykładzie 18.5. Ustawia ona instrukcję strony isErrorPage na true w celu wskazania, że ta strona obsługuje błędy, oraz wykorzystuje atrybut import w celu zaimportowania klas com.oreilly.servlet. JSP wykorzystuje skryptlet do ustawienia kodu stanu odpowiedzi na 500, po czym tworzy zawartość strony przy pomocy nazwy klasy wyjątku, jego ścieżkę oraz wiadomość sugerującą kontakt z administratorem WWW w celu zgłoszenia problemu.

Przykład 18.5.

A oto i problem...

<%-- bladBierz.jsp --%>

<%@ page isErrorPage="true" %>

<%@ page import="com.oreilly.servlet.*" %>

<% response.setStatus(500); %>

<HTML>

<HEAD><TITLE>Błąd: <%= exception.getClass().getName() %></TITLE></HEAD>

<BODY>

<H1>

<%= exception.getClass().getName() %>

</H1>

Wystąpił błąd podczas tworzenia oryginalnego WWW

<PRE>

<%= ServletUtils.getStackTraceAsString(exception) %>

</PRE>

<% String name = request.getServerName(); %>

Proszę skontaktować się z <A HREF="mailto:webmaster@<%= name %>">webmaster@<%= name %></A> w celu zgłoszenia błędu.

</BODY>

</HTML>

Podczas każdego żądania bladRob.jsp wywoływany jest wyjątek, kontrola jest przekazywana do bladBierz.jsp, który wyświetla elegancką stronę informującą o błędzie. Proszę zobaczyć rysunek 18.3.

Rysunek 18.3.

Własna strona błędów

0x01 graphic

Tak więc dlaczego w bladRob.jsp występuje wywołanie System.currentTime Millis()? Bierze się to z faktu, że strony JSP muszą przechowywać wszystkie puste miejsca z tekstu dokumentu. Pozwala to na wykorzystanie JSP do tworzenia nie tylko HTML, ale także XML, w którym puste miejsca mogą być niezwykle ważne. Niestety zachowanie wszystkich pustych miejsc oznacza, że prosty skryptlet:

<% throw new Exception("uups"); %>

musi wygenerować kod w celu wyświetlenia nowej linii po wywołaniu błędu — kod, który nigdy nie zostanie osiągnięty! Kompilacja tego skryptu na Tomcat-cie, generuje następujący komunikat o błędzie.

org.apache.jasper.JasperException: unable to compile class for

JSPC: \engines\jakarta-tomcat\work\localhost_8080%2Fjsp\

-0001fbladRob_0002ejspbladRob-jsp-3.java:75:

Statement not reached.

out.write("\r\n");

^

Poprzez dodanie sprawdzenia, czy System.currentTimeMillis() jest większe niż zero, kompilator przyjmuje, że istnieje możliwość, że wyjątek nie zostanie wywołany i w związku z tym nie wystąpi błąd kompilacji. Podobne problemy mogą pojawiać się w przypadku wyrażeń return i throw wewnątrz skryptletów.

Tak naprawdę istnieje wiele sztuczek powiązanych z wykorzystaniem skryptletów w JSP. Jeżeli przypadkowo napisze się skryptlet zamiast wyrażenia (poprzez pominięcie znaku równości), zadeklaruje statyczną zmienną wewnątrz skryptletu (gdzie zmienne te nie są dozwolone), zapomni przecinka (nie są konieczne w wyrażeniach, ale konieczne w skryptletach) lub napisze się cokolwiek nie będącego idealnym kodem Javy, przypuszczalnie otrzyma się dziwny komunikat o błędzie, ponieważ kompilator działa na wygenerowanym kodzie Javy, nie na pliku JSP. Aby zademonstrować problem, proszę wyobrazić sobie, że w pliku bladBierz.jsp <%= nazwa %> zostało zastąpione przez <% nazwa %>. Tomcat wygeneruje wtedy następujący błąd:

org.apache.jasper.JasperException: unable to compile class for

JSPC: \engines\jakarta-tomcat\work\localhost_8080%2Fjsp\

-0001fbladRob_0002ejspbladRob-jsp-3.java:91:

Class name not found.

name

^

Usunięcie błędu podobnego do przedstawionego powyżej często wymaga od programisty przyjrzenia się generowanemu kodowi w celu rekonstrukcji powodu błędu.

Unikanie kodu Javy na stronach JSP

Skryptlet, wyrażenia i deklaracje umożliwiają umieszczanie kodu Javy na stronie JSP. Są to narzędzia o dużej mocy, jednak ich wykorzystywanie nie jest polecane. Nawet w rękach doświadczonego programisty umieszczanie kodu na stronie jest programowaniem „nieświeżym”. Tworzy się kod Javy, ale nie bezpośrednio. W przypadku wystąpienia błędu ciężko jest go zdiagnozować z powodu dodatkowej niejasności.

Sytuacja staje się jeszcze gorsza, kiedy mechanizm ten wykorzystuje nowicjusz. Osoby nie będące programistami muszą nauczyć się Javy chociaż w pewnym stopniu, a Java nie jest zaprojektowana jako język skryptowy, nie jest też przeznaczona dla takich osób. Język taki jak JavaScipt — prostszy mniej ścisły i lepiej znany przez projektantów WWW — mógłby być lepszym rozwiązaniem, ale obsługa JavaScriptu nie jest czynnością standardową i w związku z tym jej implementacja nie jest popularna.

Ostatnią kwestią jest projekt. Jeżeli projektanci i programiści pracują nad tym samym plikiem, tworzy się wąskie gardło i dodatkowa możliwość wystąpienia błędu. Zawartość i grafika powinny być od siebie oddzielone. Aby to umożliwić i usunąć kod Javy ze strony, Java pozwala na zastosowanie JavaBeans i bibliotek własnych znaczników. Poniżej opisane zostaną JavaBeans.

JSP i JavaBeans

Jedną z najbardziej interesujących i potężnych metod wykorzystania JavaServer Pages jest współpraca z komponentami JavaBeans. JavaBeans to klasy Javy zaprojektowane do wielokrotnego wykorzystania, a nazwy ich metod i zmiennych tworzone są według pewnej konwencji, która daje im dodatkowe możliwości. Mogą one być osadzane bezpośrednio na stronie Javy przy pomocy znacznika <jsp:useBean>, czyli działania nazywanego przez JSP akcją. Komponent JavaBean może wykonywać dobrze zdefiniowane zadanie (zapytanie bazy danych, łączenie się z serwerem poczty, utrzymywanie informacji na temat klienta itp.), którego wynik jest dostępny stronie JSP poprzez proste metody dostępu. Większa ilość informacji na temat JavaBeans znajduje się pod adresem http://java.sun.com/bean i w książce „Developing JavaBeans” autorstwa Roberta Englandera (O'Reilly).

Różnica pomiędzy komponentem JavaBeans osadzonym na stronie JSP i każdą inną dodatkową klasą wykorzystywaną przez wygenerowany serwlet jest taka, że serwer WWW może traktować JavaBeans osadzone na stronie w specjalny sposób. Na przykład, serwer może automatycznie skonfigurować komponent (zwłaszcza zmienne egzemplarza) przy pomocy wartości parametrów w żądaniu klienta. Innymi słowy, jeżeli żądanie zawiera parametr nazwa, a serwer odkryje poprzez introspekcję (technikę, w której metody i zmienne klasy Javy mogą zostać określone programowo w czasie uruchomienia), że komponent posiada parametr nazwa i metodę ustawNazwe(String nazwa), serwer może automatycznie wywołać pobierzNazwe() z wartością parametru nazwa. Nie jest konieczne wykorzystanie metody getParameter().

Serwer może także automatycznie zarządzać zakresem komponentu. Komponent może zostać przypisany do konkretnej strony (gdzie jest wykorzystany raz i zniszczony), konkretnego żądania (podobne do zakresu strony poza tym, że komponent może przetrwać wewnętrzne wywołania dołączenia i przekazania), sesji klienta (w którym komponent jest automatycznie udostępniany, kiedy tan sam klient połączy się ponownie) lub aplikacji (w którym to zakresie ten sam komponent jest udostępniany całej aplikacji WWW).

Osadzanie komponentu Bean

Komponenty są osadzane na stronie JSP przy pomocy akcji <jsp:useBean>. Posiada ona następującą składnię (zależną od wielkości liter, cudzysłowy obowiązkowe):

<jsp:useBean> id="nazwa" scope="page|request|session|application"

class="nazwaKlasy" type "nazwaTypu">

</jsp:useBean>

Ustawione mogą być następujące atrybuty akcji <jsp:useBean>:

id

Określa nazwę komponentu. Jest to klucz pod którym komponent jest przechowywany, jeżeli jego zakres rozciąga się poza stronę. Jeżeli egzemplarz komponentu zapamiętany pod podaną nazwą już istnieje w zakresie strony, egzemplarz ten zostaje wykorzystany na stronie. W pozostałych przypadkach tworzony jest nowy komponent. Na przykład:

id="preferencjeUzyt"

scope

Określa zakres widoczności komponentu. Wartość musi wynosi page, request, session lub application. Jeżeli page, zmienna jest tworzona jako zmienna egzemplarza. Jeżeli request, zmienna jest przechowywana jako atrybut żądania; jeżeli session, komponent przechowywany jest w sesji użytkownika, a jeżeli application, w kontekście serwletu. Na przykład:

scope="session"

Domyślną wartością jest page.

class

Określa nazwę klasy komponentu. Jest ona wykorzystywana przy pierwszym tworzeniu komponentu. Nazwa klasy musi być podana w pełnej formie. Na przykład:

class="com.firma.sledzenie.PreferencjeUzytImpl"

Atrybut class nie jest potrzebny, jeżeli komponent istnieje już w aktualnym zakresie, ale ciągle musi wystąpić atrybut type pozwalający na stworzeni obiektu odpowiedniego typu. Jeżeli wystąpi problem ze stworzeniem danej klasy, strona JSP zgłasza wyjątek InstatiationException.

type

Określa typ komponentu, który powinien zostać pobrany z systemu, wykorzystywany do utworzenia obiektu po pobraniu z żądania, sesji lub kontekstu. Wartość typu powinna zostać podana w pełnej formie superklasy lub interfejsu dla klasy aktualnej. Na przykład:

type="com.firma.sledzenie.PreferencjeUzyt"

Jeżeli typ nie zostanie określony, domyślna wartość jest równa atrybutowi class. Jeżeli typ nie pasuje do prawdziwego typu obiektu, strona JSP zgłasza wyjątek ClassCastException.

beanName

Atrybut class może zostać zastąpiony atrybutem beanName. Różnica między nimi jest taka, że beanName wykorzystuje Beans.instatiate() do utworzenia egzemplarza komponentu, który poszukuje zserializowanej wersji komponentu (pliku .ser) przed utworzeniem egzemplarza od zera. Pozwala to na wykorzystanie komponentów prekonfigurowanych. Proszę przejrzeć dokumentację Javadoc dla Beans.instatiate() w celu uzyskania większej ilości informacji. Na przykład:

BeanName="com.firma.sledzenie.PreferencjeUzytImpl"

Główna część elementu <jsp:useBean> — wszystkie informacje pomiędzy znacznikiem otwierającym <jsp:useBean> a zamykającym </jsp:useBean> — jest interpretowane po utworzeniu komponentu. Jeżeli komponent nie zostanie utworzony (ponieważ w danym zakresie odnaleziono istniejący egzemplarz), wtedy część ta jest ignorowana. Na przykład:

<jsp:useBean id="preferencje" class=com.firma.sledzenie.PreferencjeUzytImpl">

Jeżeli ten napis jest widoczny, to utworzono nowy komponent!

<jsp:useBean>

Jeżeli nie jest konieczne umieszczenie głównej części, może zostać wykorzystany skrót XML dla pustego znacznika />:

<jsp:useBean id="preferencje" class=com.firma.sledzenie.PreferencjeUzytImpl" />

Kontrola parametrów komponentu

Akcja <jsp:setProperty> umożliwia parametrom żądania automatyczne (poprzez introspekcję) ustawianie właściwości komponentów osadzonych na stronie. Daje to komponentom automatyczny dostęp do parametrów żądania bez konieczności wywoływania getParameter(). Własność ta może zostać wykorzystana na kilka sposobów. Po pierwsze:

<jsp:setProperty name="nazwaKomponentu" property="*" />

Powyższa linia określa, że dowolny parametr żądania o tej samej nazwie i typie, co własność komponentu, powinien zostać wykorzystany do ustawienia tej własności komponentu. Na przykład, jeżeli element posiada metodę ustawWielkosc(int wielkosc) oraz żądanie posiada parametr wielkosc o wartości 12, serwer automatycznie wywoła komponent.ustawWielkosc(12) na początku obsługi żądania. Jeżeli wartość parametru nie może zostać przekonwertowana na właściwy typ, parametr jest ignorowany. (Proszę zauważyć, że pusta wartość parametru łańcuchowego jest traktowana tak, jakby parametr nie istniał, i pusta wartość łańcucha nie zostanie przypisana własności String).

Proszę zauważyć, że akcja wykorzystuje skrót XML dla pustego znacznika. Jest to bardzo ważne. Specyfikacja JSP wymaga od wszystkich akcji JSP by posiadały one właściwą formę XML, nawet kiedy są umieszczone w plikach HTML. Poniżej przedstawiono drugi sposób wykorzystania <jsp:setProperty>:

<jsp:setProperty name="nazwaKomponentu" property="nazwaWlasnosci" />

Powyższa linia nakazuje ustawienie danej własności, jeżeli istnieje parametr żądania o tej samej nazwie i typie. Na przykład, aby ustawić własność wielkosc, a nie żadną inną, należy wykorzystać następującą akcję:

<jsp:setProperty name="preferencje" property="wielkosc" />

Oto trzeci sposób wykorzystania <jsp:setProperty>:

<jsp:setProperty name="nazwaKomponentu" property="nazwaWlasnosci" param="nazwaParam" />

Powyższa linia nakazuje ustawienie danej własności, jeżeli występuje parametr żądania o danej nazwie i tym samym typie. Pozwala to parametrowi o pewnej nazwie na ustawianie własności o nazwie innej. Na przykład, aby ustawić własność wielkosc przy pomocy parametru wielkoscCzcionki:

<jsp:setProperty name="preferencje" property="wielkosc" param="wielkoscCzcionki" />

Poniżej przedstawiono ostatni sposób wykorzystania:

<jsp:setProperty name="nazwaKomponentu" property="nazwaWlasnosci" value="stala" />

Powyższa linia kodu nakazuje nadanie danej własności danej wartości, która zostanie przekonwertowana do właściwego typu, jeżeli okaże się to konieczne. Na przykład, aby ustawić domyślną wielkość na 18 z pominięciem parametru:

<jsp:setProperty name="preferencje" property="wielkosc" value="18" />

<jsp:setProperty name="preferencje" property="wielkosc" param="wielkoscCzcionki" />

Zaawansowani użytkownicy powinni wiedzieć, że atrybut value nie musi być stałą; może on zostać określony poprzez wyrażenie, przy pomocy mechanizmu nazywanego wyrażeniem atrybutu czasu uruchomienia. Na przykład, aby upewnić się, że wielkość nigdy nie spadnie poniżej 6:

<jsp:setProperty name="preferencje" property="wielkosc" param="wielkoscCzcionki"/>

<jsp:setProperty name="preferencje" property="wielkosc"

value="<%= Mat.maks(6, preferencje.pobierzWielkosc()) %>" />

Natomiast akcja <jsp:getProperty> dostarcza mechanizmu służącego do odczytywania wartości własności bez konieczności wykorzystywania kodu Javy na stronie. Jego zastosowanie jest podobne do <jsp:setProperty>:

<jsp:getProperty name="nazwaKomponentu" property="nazwaWlasnosci" />

Powyższa linia nakazuje dołączenie w jej miejscu wartości danej własności do danego komponentu. Jest ona dłuższa niż wyrażenie, ale pozwala na uniknięcie umieszczania kodu Javy na stronie. Wybór pomiędzy <jsp:getProperty> a wyrażeniem jest kwestią osobistego gustu. Proszę zauważyć, że <jsp:getProperty>, bezpośrednio osadza wartość własności, tak więc jeżeli zawiera ona znaki traktowane przez HTML jako specjalne, może to powodować problemy. Projekt Apache Struts pozwala na rozwiązanie tego problemu przy pomocy własnego znacznika <property>, co zostanie opisane w dalszej części tego rozdziału.

Powitania przy pomocy komponentu

Przykład 18.6 przedstawia zastosowanie komponentu JavaBeans na stronie JSP. Strona ta wyświetla „Witaj” przy pomocy WitajKomp.

Przykład 18.6.

Powitanie przy pomocy JavaBean

<%-- witaj3.jsp --%>

<%@ page import="WitajKomp" %>

<jsp:useBean id="witaj" class="WitajKomp">

<jsp:setProperty name="witaj" property="*" />

</jsp:useBean>

<HTML>

<HEAD><TITLE>Witaj</TITLE></HEAD>

<BODY>

<H1>

Witaj, <jsp:getProperty name="witaj" property="nazwa" />

</H1>

</BODY>

</HTML>

Jak można dostrzec, wykorzystanie komponentu JavaBeans na stronie JavaServer Pages może zredukować ilość kodu umieszczoną na stronie. Klasa WitajKomp zawiera logikę określającą nazwę użytkownika, a strona JSP działa jedynie jako szablon.

Kod WitajKomp jest przedstawiony w przykładzie 18.7. Plik jego klasy powinien zostać umieszczony w standardowym katalogu klas wspierających (WEB-INF/classes).

Przykład 18.7.

Klasa WitajKomp

public class WitajKomp {

private String nazwa = "świecie";

public void ustawNazwe(String nazwa) {

this.nazwa = nazwa;

}

public String pobierzNazwe() {

return nazwa;

}

}

Jest to chyba najprostszy możliwy komponent. Posiada pojedynczą własność nazwa ustawianą przy pomocy ustawNazwe(), a odczytywaną przy pomocy pobierzNazwe(). Domyślną wartością nazwa jest świecie, ale kiedy nadchodzi żądanie zawierające parametr nazwa, własność jest automatycznie ustawiana przez serwer przy pomocy wywołania ustawNazwe(). W celu przetestowana mechanizmu, proszę obejrzeć stronę http://serwer:port/witaj3.jsp. Powinien pojawić się wynik podobny do przedstawionego na rysunku 18.4.

Rysunek 18.4.

Powitanie przy pomocy JavaServer Pages we współpracy z komponentem JavaBeans

0x01 graphic

Istnieje jedna kwestia, na którą należy zwrócić uwagę: na niektórych serwerach (włączając w to Tomcat 3.2), jeżeli istnieje komponent o zakresie session lub application, a implementacja klasy komponentu zostanie zmieniona, w późniejszych żądaniach może pojawić się ClassCastException. Wyjątek ten pojawia się, ponieważ kod generowanego serwletu musi wywołać egzemplarz komponentu, który pobierany jest z sesji lub aplikacji, a typ starego komponentu przechowywany w sesji lub aplikacji nie pasuje do spodziewanego typu nowego komponentu. Najprostszym rozwiązaniem jest ponowne uruchomienie serwera.

Dołączenia i przekazania

Poprzez połączenie instrukcji i akcji, JSP umożliwia obsługę dołączeń i przekazań. Istnieją dwa typy dołączeń i jedno przekazań. Pierwszym dołączeniem jest instrukcja include:

<%@ include file="SciezkaDoPliku" %>

Powyższe dołączenie następuje w czasie tłumaczenia, co oznacza, że następuje podczas tworzenia serwletu działającego w tle. Cała zawartość zewnętrznego pliku jest dołączana tak, jak gdyby była bezpośrednio umieszczona na stronie JSP. Programiści C mogą dostrzec jej bliskie podobieństwo do #include. Ścieżka do pliku jest zakotwiczona w katalogu macierzystym kontekstu (tak więc /indeks.jsp odnosi się do index.jsp aktualnej aplikacji WWW), a ścieżka nie może sięgać poza katalog macierzysty kontekstu (proszę nie próbować ../../../innyKontekst). Zawartość wewnątrz pliku jest zazwyczaj fragmentem strony, nie całą stroną, tak więc można pomyśleć o zastosowaniu rozszerzenia pliku .inc lub .jin w celu wskazania tego faktu. Poniższa strona JSP wykorzystuje kilka instrukcji dołączenia w celu utworzenia strony ze zbioru elementów (elementy nie są pokazane):

<%@ include file="/naglowek.html" %>

<%@ include file="obliczNazwe.inc" %>

Witaj, <%= nazwa %>

<%@ include file="/stopka.html" %>

Skąd pochodzi zmienna nazwa? Jest ona tworzona przez plik obliczNazwe.inc. Ponieważ zawartość dołączanego pliku jest dołączana bezpośrednio, nie występuje oddzielenie zakresu zmiennej.

Drugim typem dołączenia jest akcja <jsp:include>:

<jsp:include page="sciezkaDoZasobuDynamicznego" flush="true" />

Powyższe dołączenie następuje w czasie żądania. Nie jest to surowe dołączenie takie, jak w instrukcji include; zamiast tego serwer uruchamia określony zasób dynamiczny i dołącza jego wynik do zawartości wysyłanej do klienta. Niewidocznie akcja <jsp:include> generuje kod, który wywołuje metodę include() RequestDispatcher, tak więc do <jsp:include> stosują się te same zasady wykonania, co do include() — dołączana strona musi być dynamiczna, nie może ustawiać kodu stanu, nie może ustawiać żadnych nagłówków, a ścieżka strony jest zakotwiczona w katalogu macierzystym kontekstu. Wymagany jest atrybut flush, który w JSP 1.1 musi być ustawiony na true. Wskazuje to na konieczność opróżnienia bufora odpowiedzi przed dołączeniem. Zważywszy na możliwości Servlet API 2.2, wartość false nie może być obsługiwana; spodziewane jest, że JSP 1.2 utworzone na podstawie Servlet API 2.3 będzie zezwalać na wartość false.

Jako przykład, poniższa akcja <jsp:include> dołącza zawartość generowaną przez wywołanie strony powitanie.jsp. Strona powitanie.jsp może wyglądać podobnie do witaj1.jsp poza tym, że pożądane może być usunięcie otaczającego kodu HTML sprawiającego, że witaj1.jsp jest kompletną stroną HTML.

<jsp:include page="/powitanie.jsp" />

Akcja <jsp:include> może opcjonalnie zawierać w swojej głównej części dowolną ilość znaczników <jsp:param>. Znaczniki te dodają parametry do dołączanego żądania. Na przykład, poniższa akcja przekazuje powitaniu domyślną nazwę:

<jsp:include page="/powitanie.jsp">

<jsp:param name="domyslnaNazwa" value="Nowy użytkownik" />

</jsp:include>

JSP może także przekazywać żądanie przy pomocy akcji <jsp:forward>:

<jsp:forward page="sciezkaDoZasobuDynamicznego" />

Przekazanie powoduje przejęcie kontroli obsługi żądania przez konkretny zasób. Tak jak w przypadku akcji <jsp:include> jest ono wykonywane w czasie żądania i jest oparte na metodzie forward() RequestDispatcher. Stosowane są zasady wykorzystania forward() — w czasie przekazania cały zbuforowany wynik zostaje wyczyszczony, a jeżeli stało się to wcześniej, system zgłasza wyjątek. Poniższa akcja przekazuje żądanie specjalnej stronie, jeżeli użytkownik nie jest zalogowany:

<% if (session.getAttribute("uzyt") == null { %>

<jsp:forward page="/logowanie.jsp" />

<% } %>

Akcja <jsp:forward> również przyjmuje znaczniki <jsp:param>.

Aplikacja „Narzędzia”

W tym momencie, znając już zasady dołączeń i osadzania JavaBeans na stronach JSP, można wykorzystać te własności do stworzenia aplikacji wyświetlającej narzędzia. Na stronie JSP zostanie osadzony komponent dający stronie dostęp do informacji na temat narzędzi i pozwalający na traktowanie strony jako przeglądarki tych danych. Przykład 18.8 przedstawia stronę JSP o nazwie widoknarz.jsp.

Przykład 18.8.

Aplikacja przeglądająca narzędzia przy pomocy JSP

<%-- widoknarz.jsp --%>

<%

String tytul = "Lista narzędzi";

String tytul2 = "Lista narzędzi do tworzenia zawartości";

String opis = " Bez narzędzi, ludzie nie są niczym więcej niż zwierzętami. " +

I to dość słabymi. Poniżej przedstawiono listę opartych na " +

serwletach narzędzi do tworzenia zawartości, które można " +

wykorzystać w celu wzmocnienia się.";

%>

<%@ include file="/naglowek.jsp" %>

<%@ page session="false" %>

<%@ page errorPage="/bladBierz.jsp" %>

<jsp:useBean id="narzkomp" class="NarzKomp" scope="application">

<jsp:setProperty name="narzkomp" property="plikNarz"

value='<%= application.getInitParameter("plikNarz") %>' />

</jsp:useBean>

<%

Narzedzie[] narzedzia = plikNarz.pobierzNarz(request.getParameter("stan"));

for (int i = 0; i < narzedzia.length; i++) {

Narzedzie narzedzie = narzedzia[i];

%>

<HR SIZE=2 ALIGN=LEFT>

<H3>

<%= narzedzie.nazwa %>

<% if (narzedzie.czyNowy(45)) { %>

<FONT COLOR=#FF0000><B> (Nowość!) </B></FONT>

<% } else if (narzedzie.czyUaktualniony(45)) { %>

<FONT COLOR=#FF0000><B> (Uaktualniony!) </B></FONT>

<% } %>

</H3>

<A HREF="<%= narzedzie.domURL %>"><%= narzedzie.domURL %></A><BR>

<%= narzedzie.komentarz %>

<% } %>

<%@ include file="/stopka.jsp" %>

Na początku wewnątrz skryptletu, jako łańcuchy String, definiowane są tytuły i opis strony. Następnie wykorzystana jest instrukcja include w celu dołączenia standardowej zawartości nagłówka z pliku naglowek.jsp oraz standardowej zawartości stopki (na końcu pliku) z pliku stopka.jsp. Instrukcja include umieszcza zawartość plików nagłówka i stopki do pliku podczas fazy tłumaczenia, tak więc zmienne tytul, tytul2 i opis są widoczne wewnątrz dołączanych plików, i plik naglowek.jsp wykorzystuje te zmienne. Pliki naglowek.jsp i stopka.jsp są przedstawione w przykładach 18.9 i 18.10.

Przykład 18.9.

Plik naglowek.jsp

<%-- naglowek.jsp --%>

<%-- Zależy od obecności zmiennych tytul, tytul2 i opis --%>

<HTML><HEAD><TITLE><%= tytul %></TITLE></HEAD>

<BODY BGCOLOR="#FFFFFF" BACKGROUND="/obrazki/tlo.gif"

LINK="#003333" ALINK="#669999" VLINK="#333333">

<IMG SRC="/obrazki/banner.gif" WIDTH=600 HEIGHT=87 BORDER=0><BR>

<TABLE>

<TR>

<TD WIDTH=125 ROWS=10 VALIGN=TOP>

<BR><BR><BR>

<FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#FF0000">

<A HREF="/indeks.html">Strona główna</A><BR>

<A HREF="/hosting.html">Hosting</A><BR>

<A HREF="/mechanizmy.html">Mechanizmy</A><BR>

</FONT>

</TD>

<TD WIDTH=475>

<TABLE CELLPADDING=5><TR><TD WIDTH=600 BGCOLOR="#006699" VALIGN=TOP>

<B><FONT FACE="Arial,Helvetica" SIZE="+2">

<%= tytul %>

</FONT></B>

</TD></TR></TABLE>

<B><FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#003366">

<%= tytul2 %>

</FONT></B><P>

<P>

<FONT FACE="Arial,Helvetica">

<%= opis %>

Przykład 18.10.

Plik stopka.jsp

<%-- stopka.jsp --%>

<%-- Nie zależy od obecności żadnych zmiennych --%>

</FONT>

</TD>

</TR>

<TR>

<TD></TD>

<TD WIDTH=475 ALIGN=CENTER COLSPAN=3>

<HR>

<FONT FACE="Arial,Helvetica">

<A HREF="/indeks.html">Strona główna</A>&nbsp;&nbsp;

<A HREF="/hosting.html">Hosting</A>&nbsp;&nbsp;

<A HREF="/mechanizmy.html">Mechanizmy</A>&nbsp;&nbsp;<P>

</FONT>

<TABLE WIDTH=100%>

<TR>

<TD WIDTH=260 ALIGN=LEFT VALIGN=TOP>

<FONT FACE="Arial,Helvetica">

<A HREF="/wlasnosc.html">Własność</A> &copy; 2000 Jason Hunter<BR>

Wszystkie prawa zastrzeżone.</TD>

<TD WIDTH=5></FONT></TD>

<TD WIDTH=230 ALIGN=RIGHT VALIGN=TOP>

<FONT FACE="Arial,Helvetica">

Kontakt: <A HREF="mailto:webmaster@servlets.com">webmaster@servlets.com</a>

</FONT></TD>

</TR>

</TABLE>

</TD>

</TR>

</TABLE>

</BODY>

</HTML>

Następnie na stronie widoknarz.jsp wykorzystana zostaje akcja <jsp:useBean> w celu osadzenia komponentu JavaBean o nazwie narzkomp, będącego egzemplarzem klasy NarzKomp. Komponent posiada zakres application, tak więc pojedynczy egzemplarz danych zostanie automatycznie zachowany w kontekście i udostępniony wszystkim stronom.

Wewnątrz głównej części znacznika <jsp:useBean> umieszczona została akcja <jsp:setProperty>, która ustawia własność komponentu plikNarz na wartość parametru inicjacji kontekstu plikNarz, wykorzystując wyrażenie atrybutu czasu uruchomienia w celu odczytania wartości. Informuje to komponent, którego pliku powinien użyć do pobrania informacji na temat narzędzi. Idealnym rozwiązaniem byłoby, jeżeli komponent mógłby uzyskać bezpośredni dostęp do zmiennej aplikacji w celu odczytania tych wartości, ale komponenty mają dostęp jedynie do informacji udostępnionych przez stronę JSP. Eleganckim rozwiązaniem byłaby możliwość akceptowania przez komponent nazwy pliku jako argumentu jego konstruktora, ale egzemplarze komponentów są tworzone przy pomocy domyślnego, nie posiadającego argumentów konstruktora, tak więc to również nie jest możliwe. Metoda zastosowana powyżej to dołączenie akcji <jsp:setProperty> do głównej części znacznika <jsp:UseBean> tak więc podczas pierwszego konstruowania komponentu bezpośrednio otrzymuje on nazwę pliku, z którego powinien pobierać informacje.

Należy zauważyć dwie sprawy dotyczące wyrażenia atrybutu czasu uruchomienia. Po pierwsze, konieczne było wykorzystanie pojedynczych nawiasów dookoła wartości, ponieważ podwójne cudzysłowy zostały zastosowane wewnątrz wyrażenia. Pozwala to na uniknięcie błędów analizatora JSP. Możliwe jest również wyłączenie podwójnych cudzysłowów z wyrażenia przy pomocy lewych ukośników. Po drugie, jeżeli parametr inicjacji nie istnieje i getInitParameter() zwraca null, wywołany zostaje wyjątek JspException, ponieważ wartość null nie jest dozwolona w wyrażeniu atrybutu czasu uruchomienia.

Po znaczniku <jsp:useBean> skryptlet wykorzystany zostaje w celu pobrania tablicy narzędzi z komponentu, co wykonywane jest poprzez wywołanie metody pobierzNarzedzia() z wartością parametru stan, na wypadek gdyby użytkownik chciał przeglądać jedynie narzędzia o określonym stanie. Kuszące może być pragnienie napisania akcji <jsp:setProperty>, która umożliwiłaby komponentowi bezpośredni dostęp do wartości parametru stan ale, ponieważ komponent posiada zakres application, nie jest to możliwe — wykonanie tego spowodowałoby problemy z współbieżnością, gdyby klika żądań próbowało ustawić tę samą własność pojedynczego współdzielonego egzemplarza komponentu.

Po odczytaniu tablicy narzędzi, wykorzystany zostaje kolejny fragment kodu skryptletu w celu rozpoczęcia pętli for dokonującej iteracji na tablicy. Wewnątrz pętli for umieszczona zostaje logika wyświetlająca każde osobne narzędzie. Wyrażenia zostają wykorzystane do wyświetlenia prostych wartości takich, jak nazwa narzędzia, URL i komentarz. Skryptlety służą do wyświetlania uwag na temat stanu — Nowość lub Uaktualnienie. Kod klasy NarzKomp przedstawiony jest w przykładzie 18.11. Kod klasy Narzedzie jest identyczny jak ten przedstawiony w rozdziale 14, „Szkielet Tea”.

Przykład 18.11.

Klasa wspierająca NarzKomp

import java.util.*;

public class NarzKomp {

private Narzedzie[] narzedzia;

private String stan;

public NarzKomp() { }

public void ustawPlikNarz(String plikNarz) throws Exception {

// Nie ma sposobu dostępu do kontekstu aplikacji bezpośrednio z komponentu

narzedzia = Narzedzie.ladujNarz(plikNarz);

}

public Narzedzie[] pobierNarz(String stan) throws Exception {

if (narzedzia == null) {

throw new IllegalStateException(

"Zawsze należy ustawić własność plikNarz NarzKomp");

}

if (stan == null) {

return narzedzia;

}

else {

// Zwrócenie jedynie narzędzi posiadających dany „stan”

List lista = new LinkedList();

for (int i = 0; i < narzedzia.length; i++) {

if (narzedzia[i].getStateFlag().equalsIgnoreCase(stan)) {

lista.add(narzedzia[i]);

}

}

return (Narzedzie[]) lista.toArray(new Narzedzie[0]);

}

}

}

Metoda ustawPlikNarz() jest automatycznie wywoływana przez akcję <jsp:setProperty> zaraz po skonstruowaniu elementu. Pobiera ona informacje na temat narzędzi z określonego pliku.

Metoda pobierzNarz() jest wywoływana przez stronę JSP bezpośrednio wewnątrz skryptletu. Metoda pobiera jako parametr stan narzędzi, według którego będą one sprawdzane. Jeżeli stan posiada wartość null, zwracana jest pełna lista. Jeżeli zmienna narzędzi posiada wartość null oznacza to, że ustawPlikNarz() nie zakończyła się sukcesem, a komponent zgłasza wyjątek IllegalStateException, który ostrzega użytkownika przed problemem.

Biblioteki własnych znaczników

Na koniec opisany zostanie najbardziej interesujący aspekt JavaServer Pages — obsługa bibliotek własnych znaczników. Własne znaczniki (znane także jako własne akcje) pozwalają stronie JSP na zawieranie znaczników XML które, chociaż wyglądają jak HTML, tak naprawdę pozwalają na wykonywanie konkretnego kodu Javy, kiedy wystąpi znacznik. Możliwe zastosowania bibliotek własnych znaczników są naprawdę interesujące. Na przykład, firma Live Software (twórca Jrun) wykorzystała własne znaczniki JSP do zaimplementowania całkowicie przenośnej wersji znaczników Cold Fusion Allaire o nazwie CF_Anywhere. (Krótko później Live Software została wykupiona przez Allaire.)

Szczegółowe zasady tworzenia biblioteki własnych znaczników są dość skomplikowane i wykraczają poza zakres niniejszego rozdziału. Na szczęście tworzonych i już przydatnych jest kilka bibliotek znaczników Open Source. Dwie najbardziej znane to Apache Taglibs i Apache Struts, obie pochodzące z projektu Apache Jakarta znajdującego się pod adresem http://jakarta.apache.org. Apache Taglibs zawiera biblioteki znaczników o ogólnym przeznaczeniu; Apache Struts zawiera bibliotekę znaczników przeznaczoną do obsługi architektury w stylu Model 2, ale duża ilość znaczników jest ogólnie przydatna. Lista bibliotek znaczników JSP jest dostępna pod adresem http://jsptags.com. Podejmowane są również próby stworzenia standardowej biblioteki znaczników występujące jako formalny proces żądania specyfikacji Javy o kodzie JSR-052. Większa ilość informacji na temat JSR-052 jest dostępna pod adresem http://java.sun.com/aboutJava/communityprocess/jsr/jsr_052_jsptaglib.html.

Stosowanie bibliotek własnych znaczników

Własne akcje umożliwiają osadzanie logiki aplikacji na stronach JSP przy pomocy znaczników podobnych do HTML. Przy pomocy odpowiedniego zestawu znaczników logika, która wcześniej musiała być utworzona przy pomocy skryptletów może być napisana przy pomocy znaczników. Na przykład, projekt Apache Struts posiada znacznik <iterate>, który może zastąpić pętlę for. Znacznik <iterate> powtarza główną część znacznika raz dla każdego elementu ze zbioru (List, Set, Vector, Map, tablica itp.). Podczas każdej iteracji umieszcza element, który może zostać wykorzystany w głównej części, w zakresie page. Struts posiada również znacznik <property>, który działa jak <jsp:getProperty> z dodatkowym usprawnieniem takim, że filtruje zawartość wyświetlaną przez HTML poprzez konwersję wszystkich znaków specjalnych HTML na odpowiadające im encje znakowe. Na przykład < staje się %lt;.

Poprzez wspólne wykorzystanie <iterate> i <property> można utworzyć poniższy fragment strony wyświetlający wszystkie cookies wysłane w żądaniu — z właściwie przefiltrowanymi wszystkimi znakami specjalnymi:

<%@ taglib uri="/WEB-INF/struts.tld" prefix="struts" %>

<struts:iterate id="cookie" collection="<%= request.getCookies() %>">

<struts:property name="cookie" property="name" /> =

<struts:property name="cookie" property="value" /> <BR>

</struts:iterate>

Aby zainstalować i korzystać z biblioteki takiej jak Struts należy wykonać kilka czynności:

  1. Pobrać i rozpakować dystrybucje biblioteki znaczników. Struts, na przykład, jest dostępny pod adresem http://jakarta.apache.org.

  2. Umieścić klasy biblioteki znaczników Javy tak, aby mogły zostać odnalezione przez serwer. Na przykład umieścić plik struts.jar w katalogu WEB-INF/lib lub umieścić skompilowane klasy w WEB-INF/classes.

  3. Umieścić plik Tag Library Descriptor (Deskryptor Biblioteki Znaczników — TLD) wewnątrz katalogu WEB-INF. Zazwyczaj umieszcza się go w WEB-INF/tlds lub bezpośrednio WEB-INF, dokładne umiejscowienie nie jest ważne. Na przykład proszę umieścić struts.tld w WEB-INF. Plik TLD to plik danych XML dostarczający serwerowi informacji na temat każdego znacznika w bibliotece — jego nazwy, klasy, sposoby wykorzystania jego atrybutów i części głównej itd. Posiadanie plików TLD ma dodatkową zaletę — na podstawie TLD można utworzyć stronę dokumentującą bibliotekę znaczników (na przykład przy pomocy arkusza stylów XSL).

  4. Dodać pozycję <taglib> do pliku web.xml. Następnie wewnątrz niej umieścić pozycję <taglib-uri> podającą nazwę do poszukiwań biblioteki znaczników, a także <taglib-location> podającą umiejscowienie pliku TLD. Krok ten nie jest konieczny, jeżeli pozycja <taglib-uri> jest równoważna pozycji <taglib-location>. Na przykład:

<taglib> <!—jeżeli URI pasuje do umiejscowienia to jest opcjonalne -->

<taglib-uri>/WEB-INF/struts.tld</tablib-uri>

<taglib-location>/WEB-INF/struts.tld</taglib-location>

</taglib>

Pozycja <taglib-uri> musi być ścieżką URI, tak więc jej prawidłowe wartości to struts, http://jakarta.apache.org/struts, i /WEB-INF/struts.ltd. Ścieżka URI tradycyjnie odnosi się do prawdziwego umiejscowienia zasobów, ale w powyższym przykładzie została zastosowana jedynie jako unikatowy identyfikator biblioteki znaczników.

  1. Na każdej stronie JSP wykorzystującej bibliotekę własnych znaczników należy dodać poniższą instrukcję taglib. Nakazuje ona serwerowi pobranie biblioteki znaczników o podanym „URI poszukiwań” i umieszczenie tych znaczników pod przedrostkiem nazwy XML struts:

<%@ taglib uri="/WEB-INF/struts.tld" prefix="struts" %>

  1. Należy wykorzystać znaczniki na stronie JSP, upewniając się, że przedrostek nazw XML jest taki, jak zadeklarowany w instrukcji taglib:

<struts:property name="nazwakomponentu" property="nazwawlasciwosci" />

Kreator biblioteki znaczników może połączyć TLD z plikiem JAR biblioteki znaczników. W tym przypadku, zamiast wskazywać na .tld należy wskazać bezpośrednio na .jar.

Aplikacja „Narzędzia” wykorzystująca bibliotekę własnych znaczników

Wykorzystanie znaczników Struts <iterate> i <property> może uprościć stronę widoknarz.jsp. Przy okazji zademonstrowana zostanie oparta na serwletach architektura Model 2, w której serwlet otrzymuje żądanie, dodaje atrybuty do obiektu żądania i przekazuje żądanie do JSP, która działa jak szablon. Przykład 18.12 przedstawia serwlet kontrolujący.

Przykład 18.12.

Serwlet kontrolujący Model 2

import java.io.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

public class SerwletNarz extends HttpServlet {

Narzedzie[] narzedzia = null;

public void init() throws ServletException {

// Pobranie danych narzędzi w init w celu zachowania prostoty

String plikNarz =

getServletContext().getInitParameter("plikNarz"); // z web.xml

if (plikNarz == null) {

throw new ServletException("Plik danych narzędzi musi być określony jako " +

"parametr inicjacji kontekstu plikNarz");

}

log("Pobieranie narzędzi z " + plikNarz);

try {

narzedzia = Narzedzie.ladujNarz(plikNarz);

if (narzedzia.length == 0) {

log("Nie odnaleziono narzędzi w " + plikNarz);

}

else {

log(narzedzia.length + " narzędzi znaleziono w " + plikNarz);

}

}

catch (Exception w) {

throw new ServletException(w);

}

}

public void doGet(HttpServletRequest zad, HttpServletResponse odp)

throws ServletException, IOException {

Narzedzie[] narzedzia = null;

// Umieszczenie właściwego atrybutu „narzedzia” w kontekście

String stan = zad.getParameter("stan");

if (stan == null) {

zad.setAttribute("narzedzia", pobierzNarz());

}

else {

zad.setAttribute("narzedzia", pobierzNarz(stan));

}

// Przesłanie żądania do JSP w celu dalszego przetworzenia

RequestDispatcher disp = zad.getRequestDispatcher("/widoknarz-znacznik.jsp");

disp.forward(zad, odp);

}

public Narzedzie[] pobierzNarz() {

return narzedzia;

}

public Narzedzie[] pobierzNarz(String stan) {

List lista = new LinkedList();

for (int i = 0; i < narzedzia.length; i++) {

if (narzedzia[i].getStateFlag().equalsIgnoreCase(stan)) {

lista.add(narzedzia[i]);

}

}

return (Narzedzia[]) lista.toArray(new Narzedzia[0]);

}

}

Serwlet zachowuje się podobnie do NarzedziaAp w Tea i SerwletNarz w WebMacro. Pobiera informacje o narzędziach w swojej metodzie init(), a dla każdego żądania dokłada do obiektu żądania odpowiedni podzbiór narzędzi. Serwlet przekazuje każde żądanie plikowi JSP, który właściwie tworzy stronę. Posiada on dostęp do narzędzi w każdym obiekcie żądania przy pomocy znacznika <jsp:useBean>. Przykład 18.13 przedstawia plik JSP noszący nazwę widoknarz-znacznik.jsp.

Przykład 18.13.

Aplikacja „Narzędzia” przystosowana do bibliotek znaczników

<%-- widoknarz-znacznik.jsp --%>

<%@ taglib uri="/WEB-INF/struts.tld" prefix="struts" %>

<%

String tytul = "Lista narzędzi";

String tytul2 = "Lista narzędzi do tworzenia zawartości";

String opis = " Bez narzędzi, ludzie nie są niczym więcej niż zwierzętami. " +

I to dość słabymi. Poniżej przedstawiono listę opartych na " +

serwletach narzędzi do tworzenia zawartości, które można " +

wykorzystać w celu wzmocnienia się.";

%>

<%@ include file="/naglowek.jsp" %>

<%@ page session="false" %>

<%@ page errorPage="/bladBierz.jsp" %>

<%-- Ustawienie tablicy narzędzi jako atrybutów żądania--%>

<jsp:useBean id="narzedzia" class="Narzedzie[]" scope="request"/>

<struts:iterate id="narzedzie" collection="<%= narzedzia %>">

<HR SIZE=2 ALIGN=LEFT>

<H3>

<%-- Automatycznie wartości opuszczane przez HTML --%>

<struts:property name="narzedzie" property="nazwa" />

<% if (((Narzedzie)narzedzie).czyNowy(45)) { %>

<FONT COLOR=#FF0000><B> (Nowość!) </B></FONT>

<% } else if (((Narzedzie)narzedzie).czyUaktualniony(45)) { %>

<FONT COLOR=#FF0000><B> (Uaktualnienie!) </B></FONT>

<% } %>

</H3>

<A HREF="<struts:property name="narzedzie" property="domURL"/>">

<struts:property name="narzedzie" property="domURL"/></A><BR>

<%-- Przyjęcie, że w komentarzu ma nie występować HTML --%>

<struts:property name="narzedzie" property="komentarz" />

</struts:iterate>

<%@ include file="/stopka.jsp" %>

Powyższa strona po pierwsze wykorzystuje instrukcję taglib do pobrania biblioteki własnych znaczników Struts. Nie wolno zapomnieć o tym kroku! Jeżeli krok ten zostanie pominięty, nie będzie wyświetlony bezpośredni komunikat o błędzie, ale strona nie będzie działać. Powodem tego jest fakt, że bez instrukcji taglib znaczniki <struts:iterate> i <struts:property> serwer traktuje jak wszystkie inne znaczniki HTML i w związku z tym jako zawartość do wyświetlenia, a nie logikę strony do wykonania!

Główna część strony wykorzystuje znacznik <struts:iterate> w celu wykonania pętli nad tablicą narzędzi oraz znacznik <struts:property> w celu wyświetlenia nazwy, URL-a i komentarza dla każdego narzędzia. Jeżeli komentarze mogą zawierać kod HTML, właściwym znacznikiem byłby <jsp:getProperty> przeciwdziałający przefiltrowaniu zawartości HTML.

Wykorzystanie popularnych znaczników Apache Struts usuwa dużą ilość kodu ze strony HTML, pozostaje jednak kilka skryptletów. Usunięcie wszystkich skryptletów wiązałoby się z koniecznością utworzenia własnego znacznika wykonującego porównanie znaczników czasu. Niestety, z powodu skomplikowania procesu tworzenia własnych znaczników, samouczek nie został umieszczony w tej książce. Osoby zainteresowane tworzeniem własnych znaczników powinny przejrzeć książkę „JavaServer Pages” autorstwa Hansa Bergstena (O'Reilly).

Przed rozpoczęciem opisu warto zauważyć, że umieszczanie kodu Javy na stronie JSP jest uważane za działanie w złym stylu. Uważane za lepsze zaawansowane zastosowania JSP zostaną opisane w dalszej części rozdziału.

Osoby zainteresowane zobaczeniem prawdziwego kodu źródłowego serwletu dla strony JSP, w większości przypadków mogą odnaleźć go w tymczasowym katalogu określonym w atrybucie kontekstu javax.servlet.context.tempdir (proszę zobaczyć rozdział 4, „Pobieranie informacji”. Kiedy odnajdzie się prawdziwe źródło serwletu można zobaczyć, że jest ono o wiele bardziej skomplikowane niż to przedstawione w tym miejscu.

Istnieje jedna drobna różnica pomiędzy metodą include() i akcją <jsp:include> — metoda include() wymaga, żeby ścieżka do strony rozpoczynała się od /. Akcja <jsp:include> dodatkowo zezwala na względne ścieżki takie, jak ../indeks.jsp, tak długo, jak ścieżka nie wychodzi poza aktualny kontekst.

Z technicznego punktu widzenia, specyfikacja JSP nie określa sposobu postępowania z zerowymi wartościami wyrażeń atrybutów czasu uruchomienia. W związku z tym modelem staje się wzorcowa implementacja Tomcata, a Tomcat 3.2 zgłasza wyjątek.

Z uwagi na zachowujące puste miejsca zasady JSP należy być ostrożnym podczas tworzenia wyrażeń if/else wewnątrz skryptletów. Poniższy kod nie działałby:

<% if (narzedzie.czyNowy(45)) { %>

<FONT COLOR=#FF0000><B> (Nowość!) </B></FONT>

<% } %>

<% else if (narzedzie.czyUaktualniony(45)) { %>

<FONT COLOR=#FF0000><B> (Uaktualniony!) </B></FONT>

<% } %>

W przypadku powyższego kodu, serwlet działający w tle próbowałby wstawić nową linię pomiędzy klauzulami if i else, powodując paskudny błąd kompilacji — 'else' without 'if'.

Znacznik <iterate> wymaga JDK 1.2 lub późniejszego. W JDK 1.1 występuje znacznik <enumerate>.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

2 H:\Książki\!Wit\Java Servlet Programming\5 do merytorycznej\r18-05.doc



Wyszukiwarka

Podobne podstrony:
R04-05(2), Informacje dot. kompa
R15-05(2), Informacje dot. kompa
r01-05(1), Informacje dot. kompa
r03-05(1), Informacje dot. kompa
r02-05(1), Informacje dot. kompa
r16-05(1), Informacje dot. kompa
r14-05(1), Informacje dot. kompa
R07-05(3), Informacje dot. kompa
05(1), Informacje dot. kompa
r11-05(1), Informacje dot. kompa
R01-05(4), Informacje dot. kompa
R02-05(4), Informacje dot. kompa
r09-05(2), Informacje dot. kompa
R00-05(4), Informacje dot. kompa
R10-05(2), Informacje dot. kompa
R-05-07(1), Informacje dot. kompa
06(1), Informacje dot. kompa

więcej podobnych podstron