J2EE: Session Beans
Technologia J2EE zakłada podział aplikacji na warstwy:
W przypadku wykorzystania jedynie "grubego" klienta 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. (6x warstwa ;)
W wersji 1.3 J2EE składają się nań trzy rodzaje komponentów:
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.
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).
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ń:
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 zostały zaznaczone 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; 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" wyszukiwany jest w innym kontekscie. J2EE nie ogranicza nas do używania jednego kontekstu.
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; 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:
javax.ejb.EJBObject
.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:
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; 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:
javax.ejb.SessionBean
ejb
(np. create
-> ejbCreate
).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 IDLa w CORBIE.
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.
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:
©2002 Jerzy Ziemiański