Wykład 7

Sesyjne EJB

Session Enterprise JavaBeans


Streszczenie

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.


Przypomnienie

Technologia J2EE zakłada podział aplikacji na warstwy:

Każda z tych warstw może oczywiście działać na innym komputerze.

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.


Warstwa biznesowa

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

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:

Komponenty stanowe

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 bezstanowe

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ń:

Komponenty bezstanowe zapewniają dużo większą wydajność od stanowych, gdyż serwer nie musi dbać o zachowanie ich stanu.

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.


Klient

Nadeszła pora na pokazanie jak się używa komponentów sesyjnych. Można ich używać między innymi:

Jako przykład posłuży nam prosty komponent sesyjny bezstanowy.

Klient JSP

<%@ 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:

  1. Pobierany główny kontekst.
  2. Wyszukiwany interfejs domowy komponentu (o tym później).
  3. Tworzony komponent.
Później używamy komponentu jak zwykłego obiektu. Czyż nie wygląda to podobnie do CORBA? Należy jednak zaznaczyć, że oba wywołania (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.

Klient w postaci serwletu

Analogiczny do powyższego kod:

import javax.naming.Context;
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();
}
}
}
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.


Implementacja komponentu

W tym celu musimy przygotować dwa interfejsy oraz jedną klasę.

Interfejs zdalny (remote)

Definiuje metody biznesowe, czyli te, które będzie mógł wywoływać klient.

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface Converter extends EJBObject {
public double dollarToYen(double dollars)
throws RemoteException;
public double yenToEuro(double yen)
throws RemoteException;
}
Należy tutaj pamiętać o ważnych zasadach:

Interfejs domowy (home)

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;

import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface ConverterHome extends EJBHome {
Converter create() throws RemoteException, CreateException;
}
Uwagi:

Klasa 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; 
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) {}
}
Uwagi:

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.


Cykl życia

Cykl życia komponentu stanowego

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).

Cykl życia komponentu bezstanowego

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ć.


Transakcje

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:

Wyboru dokonuje się modyfikując pliki konfiguracyjne.

Transakcje kontrolowane przez komponent

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.

Transakcje kontrolowane przez kontener

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:


Użycie Deploytool

Najprostszym sposobem na utworzenie pliku EAR (Enterprise ARchive) i umieszczenie stworzonego EJB na serwerze jest użycie Deploytool. W tym celu należy: 1. Uruchomić serwer J2EE na komputerze, na którym chcemy umieścić stworzone EJB;; 2. Uruchomić Deploytool; 3. Stworzyć w deploytool nową aplikację (File -> New -> Application) lub wybrać istniejącą. Przy tworzeniu nowej aplikacji Deploytool poprosi o podanie lokalizacji dla pliku EAR i nazwy aplikacji; 4. Dodać nowe EJB do aplikacji (File -> New -> Enterprise Bean). Uruchomi to kreator nowego EJB. 5. W kreatorze należy określić "wyświetlaną" nazwę EJB, następnie (przyciskiem Edit) wskazać pliki, jakie powinny być umieszczone w archiwum (skompilowane klasy z EJB, ewentualne zasoby itp.) oraz (jeśli to konieczne) określić (wciskając przycisk Manifest Classpath), jakie zewnętrzne biblioteki są potrzebne tworzonemu EJB 6. Następnie należy wybrać rodzaj tworzonego EJB (message driven, session, entity) - w wypadku zadania z tego wykładu będzie to stateless session, podać nazwę tworzonego EJB i wskazać klasy implementujące EJB oraz interfejsy domowe i biznesowe; 7. Określić sposób obsługi transakcji (przez bean lub kontener) 8. Określić zmienne środowiskowe używane w kodzie wraz z ich wartościami, EJB do których odwołujemy się w kodzie, używane fabryki zasobów i odbiorców komunikatów JMS oraz uprawnienia wymagane do użycia poszczególnych metod (w najprostszych EJB na tych ekranach nie musimy nic robić, zostajemy przy wartościach domyślnych) 9. Przejrzeć i zweryfikować deployment descriptor 10. (Jeśli umieszczamy od razu EJB na serwerze) Wybrać z menu Tools opcję Deploy, określić nazwy JNDI rozmieszczanego komponentu (albo komponentów) aplikacji i nazw JNDI zasobów, do których odwołują się EJB aplikacji. Po wciśnięciu przycisku Finish Deploytool próbuje rozmieścić aplikację na serwerze. 11. Jeśli rozmieszczenie aplikacji się nie udało, wybrać z menu Tools opcję Verifier i zapoznać się z raportem z weryfikacji naszej aplikacji. W zależności od rodzaju błędów może okazać się konieczne poprawienie kodu źródłowego EJB i ponowna kompilacja albo jedynie zmiana ustawień w deploytool.

Podsumowanie

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).


Słownik

EJB (Enterprise JavaBeans)
Komponenty gospodarcze, które oferują swoim klientom pewne usługi; dzielą się na encyjne, sesyjne i sterowane komunikatami.
interfejs domowy
Deklaracja metod służących klientowi do tworzenia i usuwania komponentów EJB (w przypadku komponentów encyjnych dochodzi jeszcze wyszukiwanie).
interfejs zdalny
Deklaracja metod gospodarczych komponentu EJB, czyli tych, które będzie mógł wywoływać klient.
klasa komponentu
Implementacja metod gospodarczych zdefiniowanych w interfejsie zdalnym oraz odpowiedniki metod zdefiniowanych w interfejsie domowym. Jej egzemplarze są tworzone przez kontener EJB, który pośredniczy w wywołaniach wszystkich ich metod i zapewnie wszystkie usługi platformy.
komponenty sesyjne
Komponenty gospodarcze EJB, które nie reprezentują obiektów bazy danych w odróżnieniu od komponentów encyjnych.
komponenty sesyjne bezstanowe
Komponenty sesyjne, które nigdy nie są przypisane do żadnej sesji użytkownika; mogą mieć stan, jednak nie jest on skojarzony z jakimkolwiek użytkownikiem.
komponenty sesyjne stanowe
Komponenty sesyjne, które zachowują swój stan od momentu utworzenia (rozpoczęcia sesji przez klienta) aż do usunięcia (zakończenia sesji). Z chwilą przypisania takiego komponentu do sesji użytkownika inni nie mogą z niego skorzystać aż do chwili zakończenia tej sesji.
kontener EJB
Obiekt serwera J2EE obsługujący komponenty sesyjne EJB; zajmuje się ich tworzeniem, niszczeniem, przypisywaniem do żądań klientów (z dowolnej warstwy); sarządza prawami dostępu, transakcjami, zapisywaniem ich stanu oraz zapewnia wiele potrzebnych usług.

Adresy w Sieci


Zadanie

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.


Strona przygotowana przez Tomasza Pieciukiewicza.