W tym wykładzie omówimy komponenty sesyjne EJB. Są to obiekty gospodarcze, które oferują innym bytom (klientom) pewne uniwersalne usługi gospodarcze. EJB są bardzo interoperacyjne, ponieważ kontener EJB zapewnia do nich dostęp przez IIOP (protokół komunikacyjny CORBA), a więc każdy uprawniony klient CORBA może korzystać z usług EJB. EJB są też przenośne w ramach serwerów EJB. W teorii każdy zapakowany w EAR komponent powinien móc być wdrożony na dowolonym serwerze spełniającym specyfikcację EJB. W praktyce jest z tym jednak bardzo różnie.
Mechanizmy podobne do komponentów EJB nie są elementem żadnej innej platformy. Oczywiście na przykład w PHP i .NET programista może sobie coś takiego zaimplementować lub załączyć odpowiednią bibliotekę napisaną przez kogoś innego, jednak takie technologie komponentowe nie są elementem tych platform.
Technologia J2EE zakłada podział aplikacji na warstwy:
W przypadku wykorzystania jedynie klienta grubego z warstwy webowej można zrezygnować. Warstwa biznesowa w mniejszych (mniej skomplikowanych) projektach często też nie jest stosowana - jej rolę przejmuje warstwa webowa lub kliencka. Zastosowanie jednak wszystkich tych warstw zapewnia największą elastyczność i skalowalność systemu.
W wersji 1.3 J2EE składają się nań trzy rodzaje komponentów:
Wszystkie one działają w kontenerze EJB, który zajmuje się ich tworzeniem, niszczeniem, przypisywaniem do żądań klientów (z dowolnej warstwy). Zarządza prawami dostępu, transakcjami, zapisywaniem ich stanu oraz zapewnia wiele potrzebnych usług. Przypomina to działanie serwera CORBA.
Komponenty sesyjne mają na celu obsługiwanie żądań klientów. W danym momencie co najwyżej jeden klient jest przypisany do danego komponentu. Komponenty sesyjne wykonując w imieniu klientów metody zapewniają logikę biznesową aplikacji. W odróżnieniu od komponentów encyjnych nie są trwałe.
Celem istnienia komponentów sesyjnych jest realizacja i hermetyzacja pewnych usług. Te same usługi mógłby realizować dowolny obiekt Javy i nieJavy, ale komponenty sesyjne pozwalają robić to w ramach kontenera, który dba o ochronę praw dostępu, transakcje etc. Z komponentów sesyjnych można też korzystać przez klientów CORBA za pośrednictwem protokołu RMI-IIOP.
Istnieją dwa rodzaje komponentów sesyjnych:
Na ich stan składają się wszystkie pola oraz obiekty osiągalne z komponentu. Zachowują go od momentu utworzenia (rozpoczęcia sesji przez klienta) aż do usunięcia (zakończenia sesji). W związku z tym świetnie nadają się do celów, takich jak przechowywanie zawartości koszyka klienta w sklepie internetowym, czy rozdzielanie pracy pomiędzy inne komponenty w zależności od stanu klienta (np. zalogowany bądź nie).
Referencję do komponentu stanowego można przechować w strukturach sesji na serwerze i wielokrotnie zeń korzystać. Stan takiego komponentu jest przechowywany i utrzymywany do chwili porzucenia tej referencji. Gdy odwołujemy się do komponentu stanowego przez referencję, zawsze korzystamy z tego samego obiektu.
Komponenty te mogą zachowywać swój stan, jednak kontener J2EE nie ma tego obowiązku. Co więcej nie przypisuje tego samego komponentu do żądań tego samego klienta. Wszystkie komponenty traktowane są tak, jakby były identyczne (jakby ich stan nie był istotny). Kontener może dowolnie zarządzać pulą komponentów bezstanowych (tworzyć je lub kasować w razie potrzeby). Nadają się one zatem jedynie do zadań, które nie wymagają trybu konwersacyjnego. Przykłady ich zastosowań:
Z komponentu bezstanowego raz otrzymanego jako wynik metody create
można
korzystać wielokrotnie. Kontener może jednak za każdym razem przekazać
nasze wywołanie
innemu komponentowi. Może to być też ten sam komponent, z
którego ktoś już w międzyczasie
skorzystał i zmienił stan zmiennych instancyjnych. Z tego powodu,
korzystając z komponentów
bezstanowych, nie można założyć, że stan będzie przechowywany między
kolejnymi wywołaniami.
Gdy odwołujemy się do komponentu bezstanowego przez referencję,
nie zawsze korzystamy z tego samego obiektu.
Nadeszła pora na pokazanie jak się używa komponentów sesyjnych. Można ich używać między innymi:
<%@ page import="javax.ejb.*, javax.naming.*,
javax.rmi.PortableRemoteObject, java.rmi.RemoteException" %>
<%!
private Converter converter = null;
public void jspInit() {
try {
InitialContext ic = new InitialContext();
Object objRef = ic.lookup("java:comp/env/ejb/TheConverter");
ConverterHome home =
(ConverterHome)PortableRemoteObject.narrow(
objRef, ConverterHome.class);
converter = home.create();
} catch (RemoteException ex) {
...
}
}
%>
<html>
<head>
<title>Converter</title>
</head>
<body bgcolor="white">
<h1><center>Converter</center></h1>
<hr>
<p>Enter an amount to convert:</p>
<form method="get">
<input type="text" name="amount" size="25">
<br>
<p>
<input type="submit" value="Submit">
<input type="reset" value="Reset">
</form>
<%
String amount = request.getParameter("amount");
if ( amount != null && amount.length() > 0 ) {
Double d = new Double (amount);
%>
<p><%= amount %> dollars are
<%= converter.dollarToYen(d.doubleValue()) %> Yen.
<p><%= amount %> Yen are
<%= converter.yenToEuro(d.doubleValue()) %> Euro.
<%
}
%>
</body>
</html>
Wytłuszczonym drukiem zaznaczono istotne
elementy.
W metodzie jspInit
tworzony jest komponent:
dollarToYen
i yenToEuro
) mogą być zrealizowane przez dwa
zupełnie różne
obiekty w kontenerze (stan komponentu między wywołaniami nie jest
zachowany).
Gdyby komponent był sesyjny mielibyśmy gwarancję, że wszystkie
wywołania są
realizowane przez ten sam obiekt.
Analogiczny do powyższego kod:
import javax.naming.Context;Jak widać różnice są kosmetyczne. Warto zauważyć, że interfejs domowy (bazowy) jest wyszukiwany w innym kontekście. J2EE nie ogranicza nas do używania jednego kontekstu.
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import Converter;
import ConverterHome;
public class ConverterClient {
public static void main(String[] args) {
try {
Context initial = new InitialContext();
Object objref = initial.lookup
("java:comp/env/ejb/SimpleConverter");
ConverterHome home =
(ConverterHome)PortableRemoteObject.narrow(
objref, ConverterHome.class);
Converter currencyConverter = home.create();
double amount =
currencyConverter.dollarToYen(100.00);
System.out.println(String.valueOf(amount));
amount = currencyConverter.yenToEuro(100.00);
System.out.println(String.valueOf(amount));
currencyConverter.remove();
} catch (Exception ex) {
System.err.println("Caught an unexpected exception!");
ex.printStackTrace();
}
}
}
W tym celu musimy przygotować dwa interfejsy oraz jedną klasę.
Definiuje metody biznesowe, czyli te, które będzie mógł wywoływać klient.
import javax.ejb.EJBObject;Należy tutaj pamiętać o ważnych zasadach:
import java.rmi.RemoteException;
public interface Converter extends EJBObject {
public double dollarToYen(double dollars)
throws RemoteException;
public double yenToEuro(double yen)
throws RemoteException;
}
javax.ejb.EJBObject
.Nazywany też bazowym. Definiuje metody służące klientowi do tworzenia i usuwania obiektu (w przypadku komponentów encyjnych dochodzi jeszcze wyszukiwanie).
import java.io.Serializable;Uwagi:
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface ConverterHome extends EJBHome {
Converter create() throws RemoteException, CreateException;
}
javax.EJB.EJBHome
.create
muszą zwracać
interfejs zdalny komponentu.Implementuje metody biznesowe (zdefiniowane w interfejsie zdalnym, choć nie trzeba tego jawnie wskazywać) oraz odpowiedniki metod służących do tworzenia komponentów (te zdefiniowane w interfejsie domowym ale o trochę innych nazwach).
import java.rmi.RemoteException;Uwagi:
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
public class ConverterEJB implements SessionBean {
public double dollarToYen(double dollars) {
return dollars * 121.6000;
}
public double yenToEuro(double yen) {
return yen * 0.0077;
}
public ConverterEJB() {}
public void ejbCreate() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext sc) {}
}
javax.ejb.SessionBean
.ejb
(np. create
-> ejbCreate
).
Takich metod może być więcej pod warunkiem, że mają inne parametry
(Java dopuszcza przeciążanie metod).ejbCreate
,
ejbRemove
, ejbActivate
,
ejbPassivate
wywoływane przez kontener
przy zmianie cyklu życia komponentu (o tym później) oraz ejbSetSessionContext
.Obowiązkiem dostawcy serwera J2EE jest dostarczenie narzędzi służących do wygenerowania na podstawie powyższych źródeł klas gotowych do użycia po stronie serwera oraz po stronie klienta. Jest to proces podobny do generowania trzonu (stub) oraz klasy pośredniczącej (proxy) przez kompilator IDL w CORBA.
W tym miejscu nie widać jeszcze różnicy w implementacji komponentu stanowego i bezstanowego. Przyczyna jest prosta: takiej różnicy w kodzie nie ma. Rodzaj komponentu ustala się w plikach konfiguracyjnych.
Metoda ejbCreate
jest wywoływana w
chwili utworzenia
komponentu. Można w niej zainicjować jego stan (np. na podstawie
argumentów
wywołania) oraz zdobyć potrzebne zasoby (np. otworzyć połączenie z bazą
danych).
Metoda setSessionContext
jest
wywoływana zaraz po utworzeniu
komponentu. Kontener przekazuje jako jej argument referencję do obiektu
SessionContext
dającego dostęp do usług
oferowanych przez
kontener. Przekazaną referencję warto zapisać w zmiennej isntancyjnej.
Metoda ejbRemove
jest wywoływana w
chwili niszczenia komponentu
(wywołanie remove
przez klienta lub
porzucenie wszystkich
referencji do komponentu). Należy w niej zwolnić wszystkie przydzielone
zasoby (np. zamknąć połączenie z bazą danych).
Metoda ejbPassivate
jest wywoływana
w chwili, gdy kontener postanawia tymczasowo przenieść komponent do
pamięci
dyskowej. Należy w niej zwolnić wszystkie przydzielone zasoby (np.
zamknąć połączenie z bazą danych).
Metoda ejbActivate
jest wywoływana w
chwili, gdy kontener postanawia odtworzyć komponent na podstawie zapisu
w pamięci dyskowej. Należy w niej odzyskać wszystkie potrzebne
zasoby (np. ponownie otworzyć połączenie z bazą danych).
W wypadku komponentu bezstanowego nie ma aż takiego bogactwa
metod.
Przyczyna jest prosta -- nie dba się tu o zachowanie stanu. Komponent
bezstanowy może mieć tylko jedną metodę ejbCreate
i do tego
bezargumentową. Jest ona wywoływana w chwili utworzenia komponentu
przez
kontener. Z jednego komponentu różni klienci mogą
wielokrotnie korzystać
(po drodze wołając create
z interfejsu
domowego). Te klienckie
wywołania create
nie powodują jednak
wywołania za każdym razem
ejbCreate
tak jak to jest w wypadku komponentu
stanowego. Wywołanie create
przez klienta
daje mu dostęp do komponentu.
to kontener decyzuje, czy dać klientowi nowy komponent (wtedy woła ejbCreate
),
czy też dać mu jakiś istniejący (wtedy nie woła
ejbCreate
).
Gdy kontener uzna, że dany komponent bezstanowy nie jest mu
potrzebny,
wywołuje jego metodę ejbRemove
i niszczy go.
Tak jak w wypadku
ejbCreate
to kontener a nie klient decyduje,
kiedy tę metodę
zawołać.
Szczegółowo będzie o tym w jednym z następnych wykładów. Tu powiemy o tym tylko w skrócie. Są dwa sposoby kontrolowania transakcji:
Jeśli wybierzemy tę metodę to skazani jesteśmy na ręczne kontrolowanie transakcji. Możemy do tego celu wykorzystać Java Transaction API (JTA) oraz JDBC.
W tym wypadku cała praca sprowadza się do wskazania (w plikach konfiguracyjnych) atrybutów transakcji, które chcemy używać. Całą resztą zajmie się kontener. Do dyspozycji mamy sześć atrybutów uwzględnianych podczas wywoływania metod komponentu:
W tym wykładzie omówiliśmy sesyjne komponenty EJB razem z ich dwoma wariantami: stanowym i bezstanowym. Poznaliśmy też ich strukturę, cykl życia oraz sposoby wykorzystania. Poruszylismy też kwestię transakcji, które mogą być obsługiwane przez kontener lub przez sam komponent.
Komponenty sesyjne są ważnymi elementami wielu aplikacji, ponieważ stanowia obudowanie złożonych usług gospodarczych. Zwykle należą do warstwy kontolera aplikacji (wg modelu MVC).
Napisać komponent bezstanowy, który będzie oferował
metodę Map[] trescTabeli(String nazwaTabeli)
zwracającą zawartość tabeli bazodanowej w postaci tablicy
odwzorowań (java.util.Map
). Każde takie
odwzorowanie to jeden wiersz. Odzworowanie
przypisuje nazwom kolumn ich wartości w tym wierszu. Należy zapewnić
także klienta tego komponetu
w postaci serwletu lub JSP. Należy skorzystać z bazy danych Cloudscape
wbudowanej
w referencyjną implementacją J2EE.
Termin: 31 maja godzina 24:00.
Do tej godziny należy przysłać wykładowcy, kod źródłowy składowych komponentu, klienta i plik EAR z działającą aplikacją J2EE do wdrożenia.