J2EE: Session Beans

1. Spis treści

  1. Spis treści
  2. Przypomnienie
  3. Warstwa biznesowa
  4. Komponenty sesyjne
  5. Klient
  6. Implementacja komponentu
  7. Cykl życia
  8. Transakcje
  9. Odnośniki

2. 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 "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 ;)

3. Warstwa biznesowa

W wersji 1.3 J2EE składają się nań trzy rodzaje komponentów:

Wszystkie one działają w kontenerze J2EE, 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 CORBY.

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

Istnieją dwa rodzaje komponentów sesyjnych:

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

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

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

5.1. 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 zostały zaznaczone 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 CORBY ? 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.

5.2. 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" wyszukiwany jest w innym kontekscie. J2EE nie ogranicza nas do używania jednego kontekstu.

6. Implementacja komponentu

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

6.1. 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:

6.2. Interfejs domowy (home)

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:

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

7. Cykl życia

7.1. Cykl życia komponentu stanowego

7.1. Cykl życia komponentu bezstanowego

8. Transakcje

Są dwa sposoby kontrolowania transakcji:

Wyboru dokonuje się modyfikując pliki konfiguracyjne.

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

8.2. 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:

9. Odnośniki

©2002 Jerzy Ziemiański