r11-05.doc

(106 KB) Pobierz
Szablon dla tlumaczy

W niniejszym rozdziale:

·         Dzielenie informacji

·         Dzielenie kontroli

Rozdział 11.

Współpraca serwletów

Serwlety pracujące wspólnie na jednym serwerze mogą porozumiewać się ze sobą na kilka sposobów. Istnieją dwa główne typy współpracy serwletów:

·         Dzielenie informacji — dwa lub więcej serwletów dzieli pewien zbiór zasobów. Na przykład, zbiór serwletów zarządzających sklepem online może współdzielić zbiór inwentarza sklepu lub połączenie z baza danych. Specjalnym przypadkiem dzielenia informacji jest śledzenie sesji (proszę zobaczyć rozdział 7, „Śledzenie sesji”)

·         Dzielenie kontroli — dwa lub więcej serwletów dzieli kontrolę żądania. Na przykład, jeden serwlet może otrzymywać żądanie, ale pozwalać innemu serwletowi na przejęcie części lub całej odpowiedzialności za jego obsługę

W przeszłości (przed pojawieniem się Servlet API 2.1) wymieniona zostałaby inna technika współpracy — manipulacja bezpośrednia. W tej technice serwlet może otrzymać odwołanie do innego poprzez metodę getServlet() i wywoływać jego metody. Ta metoda współpracy nie jest już obsługiwana; metoda getServlet() została wyłączona i zdefiniowana tak, by zwracała null w przypadku Servlet API 2.1 i późniejszych. Powód — serwlet może zostać zniszczony przez serwer WWW w dowolnym momencie, tak więc nic poza serwerem nie powinno posiadać bezpośredniego odwołania do serwletu. Wszystkie działania, które mogły być wykonywane przy pomocy getServlet() mogą być wykonane w sposób lepszy i bezpieczniejszy przez alternatywy opisane w niniejszym rozdziale.

Dzielenie informacji

Serwlety często współpracują poprzez dzielenie pewnych informacji. Informacje te mogą być informacjami o stanie, współdzielonymi zasobami, źródłem zasobów, lub czymkolwiek innym. W Servlet API 2.0 i wcześniejszych nie istniały wbudowane mechanizmy, przy pomocy których serwlety mogły dzielić informacje, a w pierwszym wydaniu niniejszej książki (napisanej według Servlet API 2.0) musiały zostać przedstawione pewne kreatywne sposoby pracy, między innymi umieszczanie informacji na liście właściwości System! Na szczęście sposoby te nie są już potrzebne, ponieważ począwszy od Servlet API 2.1 rozszerzona została klasa ServletContext, która działa na zasadzie składnicy współdzielonych informacji.

Współdzielenie przy pomocy ServletContext

Serwlet odczytuje ServletContext swojej aplikacji WWW przy pomocy wywołania getServletContext(). Serwlet może wykorzystywać kontekst tak, jakby był on zmienną typu Hashtable lub Map, przy pomocy następujących metod:

public void ServletContext.setAttribute(String nazwa, Object o)

public Object ServletContext.getAttribute(String nazwa)

public Enumeration ServletContext.getAttributeNames()

public void ServletContext.removeAttribute(String nazwa)

Metoda setAttribute() dowiązuje obiekt pod podaną nazwą. Każde istniejące dowiązanie o takiej samej nazwie zostaje usunięte. Nazwy atrybutów powinny stosować tę samą konwencję, co nazwy pakietów, w celu uniknięcia nadpisywania siebie. Nazwy pakietów java.*, javax.* i com.sun.* są zarezerwowane.

Metoda getAttribute() odczytuje obiekt dowiązany pod podaną nazwą lub zwraca null, jeżeli pakiet taki nie istnieje. Wywołanie może również odczytywać specyficzne dla danego serwera, zakodowane atrybuty (na przykład javax.servlet.context.tempdir), jak opisano w rozdziale 4, „Pobieranie informacji”.

Metoda getAttributeNames() zwraca zmienną typu Enumeration, która zawiera nazwy wszystkich dowiązanych atrybutów lub pustą Enumeration, jeżeli nie istnieją żadne dowiązania.

Metoda removeAttribute() usuwa obiekt dowiązany pod daną nazwą lub nie wykonuje żadnego działania, jeżeli atrybut nie istnieje. Dobrym pomysłem jest usuwanie niepotrzebnych już atrybutów w celu zredukowania wykorzystania pamięci.

Zastosowanie kontekstu do sprzedawania pizzy

Jako zabawny przykład, proszę wyobrazić sobie zbiór serwletów sprzedających pizzę i dzielących ofertę dnia. Serwlet administracyjny mógłby ustawiać ofertę dnia w sposób przedstawiony w przykładzie 11.1.

Przykład 11.1.

Pizzeria Italia

import java.io.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class UstawiaOferta extends HttpServlet {

 

  public void doGet(HttpServletRequest zad, HttpServletResponse odp)

                               throws ServletException, IOException {

    odp.setContentType("text/plain");

    PrintWriter wyj = odp.getWriter();

 

    ServletContext kontekst = getServletContext();

    kontekst.setAttribute("com.pizzeria.oferta.pizza", "Cappricciosa");

    kontekst.setAttribute("com.pizzeria.oferta.dzien", new Date());

 

    wyj.println("Pizza — oferta dnia została ustawiona.");

  }

}

Następnie każdy inny serwlet na serwerze może uzyskać dostęp do oferty i wyświetlić ją przy pomocy kodu przedstawionego w przykładzie 11.2.

import java.io.*;

import java.text.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class pobierzOferta extends HttpServlet {

 

  public void doGet(HttpServletRequest zad, HttpServletResponse odp)

                               throws ServletException, IOException {

    odp.setContentType("text/html");

    PrintWriter wyj = odp.getWriter();

 

    ServletContext kontekst = getServletContext();

    String pizza = (String)

      context.getAttribute("com.pizzeria.oferta.pizza");

    Date dzien = (Date)

      context.getAttribute("com.pizzeria.oferta.dzien");

 

    DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM);

    String dzisiaj = df.format(dzien);

 

    wyj.println("Oferta dnia(" + dzisiaj + ") to pizza: " + pizza);

  }

}

Współdzielenie z innym ServletContext

Wykorzystanie ServletContext do współdzielenia informacji daje dodatkowy efekt w postaci posiadania przez każdą aplikację WWW swojego własnego składu informacji. Nie istnieje ryzyko przypadkowej kolizji nazw lub nawet kolizji nazw z tej samej aplikacji uruchomionej dwukrotnie na serwerze. Jednak czasami informacja musi być dzielona pomiędzy dwoma kontekstami WWW. W tej sytuacji można postąpić na dwa sposoby. Po pierwsze, wykorzystać zewnętrzny skład informacji taki, jak plik bądź bazę danych, lub, po drugie, wykorzystać specjalne punkty zaczepienia w celu bezpośredniego dostępu do innego kontekstu. Kwestia ta zostanie opisana później.

Serwlet może otrzymać uchwyt do innego kontekstu na tym samym serwerze przy pomocy punktu zaczepienia getContext() w swoim własnym kontekście:

public ServletContext ServletContext.getContext(String sciezkauri)

Metoda zwraca ServletContext zawierający podaną ścieżkę URI. Podana ścieżka musi być absolutna (rozpoczynająca się od /) i jest interpretowana według katalogu macierzystego dokumentów serwera. Metoda ta pozwala serwletowi na uzyskanie dostępu do kontekstu poza jego własnym. W środowisku wrażliwym na bezpieczeństwo lub rozpowszechnianym (proszę zobaczyć rozdział 12, „Serwlety korporacyjne i J2EE”), kontener serwletów może zwrócić null dla wszystkich ścieżek.

Aby to zademonstrować, proszę wyobrazić sobie, że serwlet poza dostępem do pizzerii musi uzyskać dostęp do oferty dnia. Proszę także przyjąć, że kontekst pizzerii znajduje się w katalogu /pizzeria. W końcu proszę założyć, że kontrola bezpieczeństwa serwera (nie dopuszczająca do komunikacji międzykontekstowej) została wyłączona. Pod tymi warunkami następujący kod pozwala dowolnemu serwletowi na serwerze na odczytanie ofert dnia z aplikacji pizzerii:

ServletContext mojKontekst = getServletContext();

ServletContext innyKontekst = mojKontekst.getContext("/pizzeria/indeks.httml");

String pizza = innyKontekst.getAttribute("com.pizzeria.oferta.pizza");

Date dzien = (Date)innyKontekst.getAttribute("com.pizzeria.oferta.dzien");

Aktualnie jedyną metodą pobierania odwołania do innego kontekstu jest wyszukiwanie ścieżki, tak więc powyższy przykład nie będzie działał, jeżeli pizzeria przeniesie się do innego URL-a. W przyszłości może pojawić się sposób na pobieranie odwołania według nazwy.

Kwestie mechanizmu ładowania klas

Jeżeli obiekt umieszczony w kontekście nie jest obiektem standardowym, dzielenie obiektu pomiędzy kontekstami staje się o wiele trudniejsze z powodu kwestii związanych z mechanizmem ładowania klas. Proszę pamiętać, że każda aplikacja WWW posiada swoje klasy w WEB-INF, ładowanym przez inny mechanizm ładowania klas. Proszę również pamiętać, że mechanizm ładowania klas jednej aplikacji nie umie zlokalizować klas umieszczonych w innej aplikacji. Nieszczęśliwy wynik — obiekt, którego plik .class istnieje jedynie wewnątrz WEB-INF/classess lub WEB-INF/lib nie może być w łatwy sposób wykorzystany przez żadną inną aplikację. Każda próba wywołania obiektu według jego właściciela daje wynik w postaci błędu NoClassDefFoundError.

Rozwiązanie w postaci skopiowania pliku .class do obu aplikacji również nie działa. Klasy ładowane przez różne aplikacje nie są tą samą klasą, nawet jeżeli ich definicje są identyczne. Próba wykonania tego daje w efekcie jedynie zamianę błędu NoClassDefFoundError na wyjątek ClassCastException.

Istnieją trzy możliwe sposoby obejścia tego problemu. Po pierwsze, skopiowanie pliku .class współdzielonego obiektu do standardowej ścieżki klas serwera (w przypadku serwera Tomcat jest to katalog_macierzysty_serwera/classes). Pozwala to na odnalezienie klasy przez podstawowy mechanizm ładowania klas współdzielony przez wszystkie aplikacje. Po drugie, uniknięcie wywoływania zwracanego obiektu i wywoływanie jego metod przy pomocy refleksji (techniki, w której klasa Javy może przeglądać i manipulować sobą w trakcie uruchomienia). Nie jest to tak eleganckie, ale pomaga, jeżeli nie posiada się pełnej kontroli nad serwerem. Po trzecie, wywołanie zwracanego obiektu do interfejsu, który deklaruje pożądane metody i umieszczenie interfejsu w standardowej ścieżce klas serwera. Każda klasa oprócz interfejsu może pozostać w pojedynczych aplikacjach WWW. Wymaga to przeprogramowania klasy dzielonego obiektu, ale pomaga w przypadkach, w których implementacja musi pozostać w swoich aplikacjach WWW.

Dzielenie kontroli

W celu bardziej dynamicznej współpracy, serwlety mogą dzielić kontrole żądania. Po pierwsze, serwlet może przekazać całe żądanie wykonując pewne wstępne przetwarzanie, po czym przekazać żądanie innemu elementowi. Po drugie, serwlet może dołączyć do swojej odpowiedzi pewną zawartość generowaną przez inny składnik, właściwie tworząc programowe dołączenie po stronie serwera. Patrząc pojęciowo, jeżeli myśli się o stronie wynikowej takiej, jak ekran, przekazanie daje innemu serwletowi pełną kontrole nad ekranem, podczas gdy dołączanie wyświetla jedynie część zawartości w pewnym miejscu ekranu.

Ta możliwość delegowania daje serwletom większą elastyczność i pozwala na lepsze rozdzielanie. Przy pomocy delegowania, serwlet może skonstruować swoją odpowiedź jako zbiór zawartości tworzonych przez różne składniki serwera WWW. Funkcjonalność ta jest szczególnie ważna dla JavaServer Pages, w której to technice często zdarza się, że jeden serwlet przetwarza żądanie, po czym przekazuje je stronie JSP w celu dokończenia zachowania spójności (proszę zobaczyć rozdział 18, JavaServer Pages).

Pobieranie dyspozytora żądań

W celu zapewnienia obsługi delegowania, Servlet API 2.1 wprowadził interfejs javax.servlet.RequestDispatcher. Serwlet pobiera egzemplarz RequestDispatcher wykonując metodę getRequestDispatcher() na pożądanym obiekcie. Metoda ta zwraca RequestDispatcher, który działa jako dyspozytor dla elementu (serwletu, JSP, pliku statycznego itp.) odnalezionego w podanej ścieżce URI:

public RequestDispatcher ServletRequest.getRequestDispatcher(String sciezka)

Przekazywana ścieżka może być ścieżką względną, chociaż nie powinna sięgać poza aktualny kontekst serwletu. Można wykorzystać opisaną w poprzednim podrozdziale metodę getContext() w celu sięgnięcia poza aktualny kontekst. Nie istnieje sposób rozciągnięcia dyspozycji na kontekst znajdujący się na innym serwerze. Jeżeli ścieżka rozpoczyna się od /, to jest interpretowana jako ścieżka względna wobec katalogu macierzystego aktualnego kontekstu, jeżeli ścieżka zawiera łańcuch zapytanie, parametry dodawane są na początku zbioru parametrów odbierającego elementu. Metoda zwraca null, jeżeli kontener serwletów nie jest w stanie zwrócić RequestDispatcher, niezależnie od powodu.

Co ciekawe, istnieje metoda o tej samej nazwie w klasie ServletContext:

public RequestDispatcher ServletContext.getRequestDispatcher(String sciezka)

Różnica pomiędzy tymi metodami polega na tym, że wersja znajdująca się w ServletContext (wprowadzona w API 2.1) przyjmuje jedynie URL-e absolutne (rozpoczynające się ukośnikiem), a wersja w ServletRequest (wprowadzona w API 2.2) akceptuje URL-e zarówno absolutne, jak i względne. W związku z tym nie ma powodu, aby stosować metodę znajdującą się w ServletContext. Istnieje ona jedynie z powodów historycznych i może być uważana za wyłączoną, choć decyzja o jej oficjalnym wyłączeniu jeszcze nie nastąpiła.

Możliwe jest również pobranie RequestDispatcher dla zasobu określonego przy pomocy nazwy, a nie ścieżki, przy pomocy metody getNamedDispatch(), znajdującej się w ServletContext:

public RequestDispatcher ServletContext.getNamedDispatcher(String nazwa)

Pozwala to na zarządzanie zasobami, które niekoniecznie są dostępne publicznie. Serwletom (a także stronom JSP) można nadawać nazwy poprzez deskryptor aplikacji WWW, jak opisano w rozdziale 3, „Cykl życia serwletów”. Metoda ta zwraca null, jeżeli kontekst nie może zwrócić dyspozytora, niezależnie od powodu.

RequestDispatcher posiada dwie metody, forward() i include(). Metoda forward() przekazuje całe żądanie innemu elementowi. Metoda include() dodaje wynik pracy innego elementu do odpowiedzi wywołującego serwletu, pozostawia jednak kontrolę w jego rękach.

Dyspozycja przekazaniem

Metoda forward() przekazuje żądanie od serwletu do innego zasobu na serwerze. Metoda pozwala jednemu serwletowi na wykonanie wstępnego przetwarzania żądania, a innemu elementowi na wygenerowanie odpowiedzi. Inaczej niż sendRedirect(), forward() działa jedynie wewnątrz serwera, a klient nie jest w stanie rozpoznać, że nastąpiło przekazanie. Informacja może zostać przekazana delegatowi przy pomocy dołączonego łańcucha zapytania lub przy pomocy atrybutów żądania ustawionych przy pomocy metody setAttribute(). Przykład 11.3 przedstawia serwlet wykonujący poszukiwanie, po czym przekierowujący jego wyniki innej stronie, która je wyświetla.

Przykład 11.3.

Wewnętrzny mechanizm wyszukiwarki

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class LogikaSzukaj extends HttpServlet {

 

  public void doGet(HttpServletRequest zad, HttpServletResponse odp)

                               throws ServletException, IOException {

    // Typ zawartości ani urządzenie wyświetlające nie jest określone

 

    // Pobranie łańcucha do poszukiwań

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

 

    // Utworzenie URL-i zawierających atrybut żądania

    String[] wyniki = pobierzWyniki(szukaj);

 

    // Określenie wyników jako atrybutu żądania

    zad.setAttribute("wyniki", wyniki);

 

    // Przekazanie do strony wyświetlającej

    String wyswietl = "/servlet/wyswietlSzukaj";

    RequestDispatcher dyspozytor = zad.getRequestDispatcher(wyswietl);

    dyspozytor.forward(zad, odp);

  }

 

  // W prawdziwym zastosowaniu metoda ta wywoływałaby właściwą logikę wyszukiwarki

  // i zwracałaby większą ilość informacji na temat każdego wyniku niż zwykły URL

  String[]pobierzWyniki(String szukaj) {

    return new String[] { "http://www.abc.com",

                          "http://www.xyz.com" };

  }

}

Zadaniem tego serwletu jest bycie mózgiem wyszukiwarki online. Wykonuje on poszukiwania tekstu podanego w parametrze szukaj, po czym przechowuje wynikowe URL-e w atrybucie żądania wyniki. Następnie serwlet przekazuje żądanie do elementu wyświetlającego. Powyżej został on sztywno zakodowany jako /servlet/wyswietlSzukaj, ale ścieżka może być utworzona tak, aby mogła podlegać zmianom zależnie od preferencji użytkownika co do języka, kolorów witryny, tego, czy jest on użytkownikiem początkującym, czy zaawansowanym, itp.

Zasady, według których działać musi serwlet przekazujący, są stosunkowo rygorystyczne:

·         Może on ustawiać nagłówki i kod stanu, ale nie może wysyłać klientowi właściwej odpowiedzi (jest o zadanie drugiego elementu). Konsekwentnie, metoda forward() musi zostać wywołana przed zatwierdzeniem odpowiedzi.

·         Jeżeli odpowiedź została już zatwierdzona, wywołanie forward() powoduje wystąpienie wyjątku IllegalStateException.

·         Jeżeli odpowiedź nie została zatwierdzona, lecz w buforze odpowiedzi istnieje zawartość, bufor zostaje automatycznie wyczyszczony — jest to część przekazywania.

·         Dodatkowo, nie jest możliwe wysyłanie nowych obiektów żądania i odpowiedzi. Metoda forward() musi zostać wywołana z tymi samymi obiektami żądania i odpowiedzi, które zostały przekazane metodzie usługowej serwletu wywołującego, a forward() musi zostać wywołana wewnątrz tego samego wątku obsługującego.

Element odbierający może zostać napisany tak, jak każdy inny element, jak przedstawia to przykład 11.4.

Przykład 11.4.

Fronton wyszukiwarki

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class WyswietlSzukaj extends HttpServlet {

 

  public void doGet(HttpServletRequest zad, HttpServletResponse odp)

                               throws ServletException, IOException {

    odp.setContentType("text/plain");

    PrintWriter wyj = odp.getWriter();

 

...

Zgłoś jeśli naruszono regulamin