MokaByte Numero  46  - Novembre 2000
Session EJB 
Un esempio applicativo
di 
Stefano Rossini
Dopo RMI e RMI-IIOP infine una panoramica sui componenti EJB per la programmazione distribuita

Prosegue l'analisi dei modelli di programmazione distribuita offerti da Java. Dopo aver parlato nel dettaglio delle tecniche di programmazione avanzata in RMI e dell'accoppiamento fra RMI e CORBA grazie a RMI-IIOP, questo mese ci avventuriamo nel mondo di EJB.

Rmi, Rmi-IIOP, Corba : i motori degli oggetti distribuiti
Negli ultimi tre numeri di Mokabyte sono stati affontati i temi di RMI [1] e RMI-IIOP [2] sia da un punto di vista teorico che con un semplice esempio.
Riassumendo brevemente le caratteristiche principali di queste soluzioni di gestione degli oggetti remoti distribuiti si può dire che :
  • RMI è una soluzione full-Java che permette in maniera semplice la realizzazione di architetture distribuite mediante utilizzo di protocollo JRMP (proprietario di Sun e non compatibile con Corba).
  • CORBA è una soluzione language-neutral, più versatile e potente ma anche complessa di RMI .
  • RMI-IIOP è una nuova versione di RMI che permette di utilizzare oltre a JRMP anche il protocollo IIOP per comunicare con applicazioni Corba e di fatto fa da ponte tra  due mondi fino ad ora incompatibili .

 

Differenza tra RMI e EJB
Come spiegato in [3] la differenza fondamentale tra EJB e RMI, Corba, RMI-IIOP è data dal supporto per la realizzazione di applicazioni distribuite.
Infatti mentre RMI e CORBA sono semplicemente dei motori di oggetti ed i cosiddetti ditribuited object services (i servizi) devono essere implementati a mano (cioè a carico del programmatore), nel modello  EJB tutto quello che riguarda la gestione dei servizi è disponibile e utilizzabile in maniera del tutto automatica e trasparente agli occhi del client.
EJB si “appoggia” a RMI  utilizzandone le funzionalità e la filosofia di base per realizzare strutture distribuite ma dà  come valore aggiunto la gestione automatica dei servizi (transazioni, sicurezza, scalabilità). 
 
 
 

L'architettura EJB
L'architettura EJB è basata essenzialmente su tre componenti: un server, i container ed i client
Rivediamo velocemente i concetti fondamentali rimandando per gli approfondimenti a [3] e [4].

EJB Container
I container EJB offrono un sistema automatico per la gestione del life-cycle dei vari componenti, delle transazioni e della sicurezza,  offrendo flessibilità e scalabilità in funzione del traffico dati e del numero dei client.
Un client non accede mai direttamente al componente, bensì interagisce sempre con l'EJB container.

Componenti
Il componente non è altro che il codice sviluppato dal programmatore  che viene “ospitato” all’interno del container.
Lo sviluppatore di un componente EJB può concentrarsi sui dettagli implementativi della business logic, tralasciando gli aspetti legati alla gestione che sono a carico del l’EJB container.
Quando si sviluppa un componente bisogna implementare due interfacce ed una classe 

  • Home interface: fornisce i metodi per creare un’istanza del componente 
  • Remote interface: definisce i metodi di business 
  • La bean implementation class (il componente) implementa la logica applicativa della Remote interface
Stateless Session Bean
Gli stateless sono componenti che non memorizzano nessun tipo di informazione al loro interno fra una invocazione e l’altra da parte del client. Essi non implementano quindi nessuna logica transazionale e per certi versi possono essere considerati come semplici contenitori di servizi. 
Un EJB stateless può essere visto come l'equivalente di una classe che non possiede attributi ma solo metodi. 
Quando il metodo viene invocato  esegue il suo computo e ritorna il risultato senza memorizzare alcun dato al suo interno.

Stateful Session Bean
I bean stateful, sono in grado di memorizzare uno stato fra due invocazioni del client permettendo, fra le altre cose, una politica di gestione delle transazioni. 
Gli stateful session bean non sono condivisi fra tutti i client che ne fanno uso(cosa che avviene per i bean stateless), dato che ognuno di essi è creato e dedicato all’utilizzo esclusivo di un client. 
La persistenza è resa possibile sempre a livello di componente senza che nessuno strumento ulteriore venga utilizzato (come ad esempio un database esterno) e questo significa che le informazioni in esso mantenute verranno perse nel momento in cui il bean cesserà di vivere (fine della sessione o per errori o crash del server).
Un EJB stateless può essere visto come l'equivalente di una classe che possiede sia attributi che metodi e può quindi memorizzare dati al suo interno.

Entity Bean
Un Entity  è un bean persistente progettato per la rappresentazione di strutture dati  nel server; fondamentalmente "mappa" al suo interno un elemento (tabella o oggetto) del database utilizzato dall’EJB server. 
In questo articolo non verranno trattati.
In figura 1 si riportano i cicli di vita dei 3 componenti.
 
 

Figura 1

 
 

EJB Server 
Il server EJB fornisce l’ambiente di esecuzione dei container e svolge il lavoro necessario sia per rendere un componente utilizzabile dai vari client remoti, sia per supportare i servizi più importanti (transazioni, mantenimento dello stato, sessioni, sicurezza) associati ai vari bean. 
 
 

Application Server EE Reference Implememntation
Per l'esempio proposto in tale articolo viene utilizzato l'Application Server Reference Implementation di Sun  essendo distribuito con la versione del J2SDKEE (vedere [4]).
Per avviare l’Application Server in ambiente Windows basta utilizzare i file batch messi a disposizione nella directory bin (per comodità è quindi consigliabile aggiungere tale percorso nel PATH ).
L’unica accortezza da seguire prima di lanciare i files batch  è quella di settare le variabili d’ambiente JAVA_HOME e J2EE_HOME in modo che puntino rispettivamente alla directory di installazione del jdk e del j2sdkee. 
A questo punto si può procedere all’avvio del database Cloudscape, utilizzando ad esempio il seguente comando

D:\j2sdkee1.2\bin>cloudscape –start

e del Reference Implementation Application Server 

D:\j2sdkee1.2\bin>j2ee
 
 
 

Un esempio di EJB Session Stateless
Riprendiamo l’esempio presentato negli articoli [1] e [2] e sviluppiamolo in chiave EJB.
Ricordo che l’esempio prevede di mettere a disposizione un servizio (oggetto remoto) registrato con nome logico MyService che restituisce come risultato la concatenazione tra due stringhe ricevute come parametri.
Il bean che dobbiamo sviluppare deve fornire il servizio di concatenazione e non ha bisogno di mantenere uno stato; svilupperemo quindi un EJB di sessione di tipo Stateless.
Si procede quindi allo sviluppo delle due interfacce Home e Remote e della bean class; poiché tali oggetti vivono in un contesto distribuito ed utilizzano il motore RMI, ogni loro metodo deve poter propagare (per mezzo di throws) la RemoteException, dato che qualcosa di non previsto può sempre accadere lungo la rete.

Home interface
La home interface svolge la funzione di factory mettendo a disposizione i metodi per la creazione del bean definendo inoltre i metodi relativi al life cycle del bean che sono invocati dal container.
La Home interface estende l'interfaccia EJBHome che a sua volta deriva dall'interfaccia Remote.
Nel caso di EJB stateless, non necessitando di inizializzazioni di variabili interne,si ha un solo metodo create senza argomenti.

/** ConcatHome.java - Home interface */
import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface ConcatHome extends EJBHome {
    Concat create() throws RemoteException, CreateException;
}

Remote Interface
Questa interfaccia definisce i metodi di business (business methods) ,ovvero le funzionalità che il client sarà in grado di utilizzare da remoto.
La Remote interface estende l'interfaccia EJBObject che a sua volta deriva dall'interfaccia Remote.

/** Concat.java - Remote Interface */
import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface Concat extends EJBObject {
   public String concat(String a, String b) throws RemoteException;
}
 

Bean class
La classe del componente fornisce l’implementazione dei business methods ma senza dover implementare la Remote interface.
Deve inoltre implementare tutti i metodi create() della Home interface anteponendo la sigla "ejb" al nome del metodo

Home interface : create(…) 
Bean class : ejbCreate(…)
 

Nel nostro caso di SessionBean, il componente deve implementare  l'interfaccia javax.ejb.SessionBean, mentre nel caso di entity bean si deve implementare l'interfaccia javax.ejb.EntityBean

/** ConcatSessionEJB.java - Session Stateless Bean per effettuare il servizio di concatenazione di due stringhe */
import java.rmi.RemoteException; 
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class ConcatSessionEJB implements SessionBean {
   public String concat(String a, String b) {
      System.out.println(new java.util.Date().toString()+"-ConcatSessionEJB.concat="+ a + b);
      return a + b;
   }
   // costruttore 
   public ConcatSessionEJB() {}
   // metodi per la gestione del ciclo di vita del componente
   public void ejbCreate() {}
   public void ejbRemove() {}
   public void ejbActivate() {}
   public void ejbPassivate() {}
   public void setSessionContext(SessionContext sctx) {}

La figura 2 riporta il diagramma UML del codice sviluppato.
 
 

Figura 2
(clicca per un ingrandimento)

 
 
 

Deploy
Dopo aver scritto e compilato il bean , si deve provvedere al suo deploy.
Per avviare il deploytool basta eseguire il batch deploytool (es:  d:\j2sdjee1.2\bin\deploytool).
Con l'Application Server Rerence Implementation del J2SDKEE non è possibile installare direttamente il bean nel Server, bensì bisogna prima creare prima un'applicazione selezionando la voce NewApplication (fig.3(1))
 

Figura 3

Si inserisce il nome ConcatApp come nome dell'applicazione (figura 4) e si seleziona la directory dove posizionare il file.ear che conterrà l'applicazione (D:\j2sdkee1.2\myejb\session\concat\ConcatApp.ear) come mostrato in figura 5.
 
 

Figura 4

 
 
Figura 5

 

A questo punto si avvia l'Enterprise Bean Wizard (fig.3(2)) al fine di :

  • creare un deployment descriptor per il bean
  • impacchettare il deployment descriptor e le classi del bean in un unico file JAR
  • inserire l’ejb.jar nel file dell'applicazione ConverterApp.ear


Si procede come prima operazione a selezionare i files che verranno inseriti nel file ConcatJAR: ConcatHome.class (Home interface), Concat.class(Remote interface) e ConcatSessionEJB.class(Bean class) aggiungendoli uno alla volta nell’area Content mediante il bottone ADD (figura 6).
 

Figura 6
(clicca per un ingrandimento)

In questa finestra si specificano quali tra i files precedentemente scelti corrispondono alla Home interface, Remote interface e bean class e si esplicita il tipo del bean che nel nostro esempio è un Session Stateles (figura 7).
 
 

Figura 7
(clicca per un ingrandimento)

Validata la finestra mediante il tasto NEXT è possibile schiacciare il tasto FINISH per concludere le operazioni visto che quelle successive riguarderanno gli Entity Bean.
A questo punto compare la lista dei files che sono stati aggiunti al file ConcatJAR.
Prima di procedere all'effettivo deploy dobbiamo assegnare un nome JNDI al bean per permettere la sua localizzazione da parte dei client.
Selezionando JNDI names  inseriamo il nome : MyService (figura 8).
 
 

Figura 8
(clicca per un ingrandimento)

Si può procedere all'effetivo deploy del bean attivando da menu il tool deploy application. Dalla voce di menu TOOLS  selezionare DEPLOY APPLICATION.
Si seleziona la casella per generare il file JAR (ConcatAppClient.jar) che dovrà essere presente sul client (figura 9). Notare che vengono generati stub per RMI-IIOP.
 

Figura 9

A questo punto compare una finestra di riepilogo che avverte che si effettuerà il deploy del componente di nome MyStatelessConcat individuabile mediante nome JNDI MyService.
Schiacciando NEXT, e alla finestra successiva FINISH, parte l’operazione di deploy del bean(figura 10).
 

Figura 10

Otteniamo il file ConcatAppClient.jar che dovrà essere presente sulla macchina del client.
 

Figura 11

Tale Jar contiene :

  • application.xml : indica il jar dell’applicazione  : <ejb>ejb-jar35615.jar</ejb>
  • sun-j2ee-ri.xml  : indica al suo interno il nome del bean   <ejb-name>MyStatelessConcat</ejb-name>  ed il suo JNDI name <jndi-name>MyService</jndi-name>
  • i file .class della Home interface, Remote interface e del bean class
  • _ConcatHome_Stub : lo stub della Home interface
  • _Concat_Stub.class : lo stub della Remote interface


Client
Vediamo infine il client che è l’utilizzatore finale del componente. 
Il client come prima operazione ottiene un reference alla Home interface localizzandola mediante il nome JNDI, cioè il nome utilizzato al momento del deploy del bean nel server EJB, che nel nostro caso è MyService.
Su tale reference si invoca il metodo create per creare un'istanza del bean per poi invocarne i metodi che mette a disposizione.

  1. HomeInterface home = Context lookup("JNDI name") + PortableRemoteObject.narrow()
  2. RemoteInterface refRemote = home.create();
  3. refRemote.businessMethod();
/** ConcatClient.java */

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

import Concat;
import ConcatHome;

public class ConcatClient {
   public static void main(String[] args) {
     try {
Context initial = new InitialContext();
Object objRef = initial.lookup("MyService");
String strFactory = System.getProperty("java.naming.factory.initial");
ConcatHome home = (ConcatHome)PortableRemoteObject.narrow(objRef, ConcatHome.class);
Concat refRemoteConcat = home.create();
String res = refRemoteConcat.concat("Hello", " EJB World !");
System.out.println(new java.util.Date().toString() + "-res="+res);
       } catch (Exception ex) {
System.err.println(" # Exception : " + ex.getMessage());
ex.printStackTrace();
       }
   } 

A questo punto si può procedere a compilare il client ed a mandarlo in esecuzione :

javac -classpath .;%J2EE_HOME%\lib\j2ee.jar ConcatClient.java
java -classpath .;%J2EE_HOME%\lib\j2ee.jar;ConcatAppClient.jar ConcatClient
 
 
 

Figura 12

 

Il client richiede due volte il servizio di concatenazione all'EJB, la prima volta per concatenare le stringhe "Hello" e " EJB World!", la seconda volta "Hello" ed " again!". 
 
 
 

Session Stateful Bean
Sviluppiamo ora un bean che effettua la concatenazione di stringhe tenendo al suo interno "traccia" di tutte le concatenazioni avvenute precedentemente.
Bisogna quindi gestire uno stato all'interno del bean e per ottenere questo si aggiunge una proprietà al bean class e (come ogni bravo bean) i metodi get/set per accedervi.
Il metodo concat effettua la concatenazione delle due stringhe ricevute in ingresso con il valore della sua proprietà result (il suo stato).

/** ConcatSessionStatefulEJB.java - EJB Session Stateful */

import java.util.*;
import javax.ejb.*;

public class ConcatSessionStatefulEJB implements SessionBean {

   private String result="";  // property 

   public void ejbCreate(String param) throws CreateException {
      System.out.println(new Date().toString() + " ConcatSessionStatefulEJB.create with param : "+ param);
      if (param == null) {
        throw new CreateException("#ERROR ! ejbCreate : Null not allowed !");
      }
      this.result = param; 
   }

   // Accessors methods
   public String getResult(){
 return this.result;
   }

   // Mutators methods
   public void setResult(String value){
 this.result = value;
   }

   // Business methods
   public String concat(String a, String b) {
      System.out.println(new java.util.Date().toString()+"-ConcatSessionStatefulEJB.concat="+ a + b);
      this.result = this.result + a + b;
 return this.result;
   }

   public ConcatSessionStatefulEJB() {}
   public void ejbRemove() {}
   public void ejbActivate() {}
   public void ejbPassivate() {}
   public void setSessionContext(SessionContext sc) {}

La Home interface, rispetto al caso precedente mette a disposizione un metodo create con un parametro String (mappato nella bean class dal metodo ejbCreate).

/** ConcatStatefulHome.java - Home interface Stateful Bean */

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

public interface ConcatStatefulHome extends EJBHome {

    ConcatStateful create(String param) throws RemoteException, 
                                                 CreateException; 
}
 

La Remote interface  pubblica oltre al metodo concat anche i metodi get/set.

/** ConcatStateful.java - Remote interface Session Stateful bean */

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

public interface ConcatStateful extends EJBObject {
   public String getResult()throws RemoteException;
   public void setResult(String value)throws RemoteException;
   public String concat(String a, String b) throws RemoteException;
}

La fase di deploy del bean è identica a quella precedentemente descritta, solo che in questo caso nella finestra di figura 6 lo scope del bean sarà  Stateful e non più Stateless.

Eseguendo un client che come nel caso precedente effettua due invocazioni allo stesso metodo con parametri diversi
otterremo in questo caso un risultato differente (figura 13).

 . . . . .
Context initial = new InitialContext();
Object objRef = initial.lookup("MyStatefulService"); // JNDI 
ConcatStatefulHome home = 
               (ConcatStatefulHome)PortableRemoteObject.narrow(objRef, 
    ConcatStatefulHome.class);
System.out.println("home.getClass().getName() = "+
            home.getClass().getName());
ConcatStateful remoteConcat = home.create("Created !");

// prima richiesta di concatenazione
String res = remoteConcat.concat("Hello ", "EJB World!");

// stampa del risultato
System.out.println(new java.util.Date().toString() +
                " - result is : "+res);
System.out.println(" ...waiting... ");
Thread.sleep(5000); 

// seconda richiesta di concatenazione
res = remoteConcat.concat("Hello "," again!!");

// stampa del risultato
System.out.println(new java.util.Date().toString() + 
        " - result is : "+res);

// stampa della proprietà result del bean
System.out.println(new java.util.Date().toString() + 
        " - result bean property : "+ remoteConcat.getResult());

// puliamo la proprietà result del bean
remoteConcat.setResult("...");

// verifichiamo il nuovo valore
System.out.println(new java.util.Date().toString() + 
        " - after clean : "+ remoteConcat.getResult());
 

Il risultato che si ottiene (figura 13) mostra come l'EJB restituisca la "storia" delle concatenazioni avvenute, dando come risultato : "Created!Hello EJB World! Hello Again!".
 
 

Figura 13

 
 
 

Bibliografia
[1] S.Rossini - "Networking in Java Parte V : RMI", Mokabyte N43, Luglio-Agosto 2000
[2] S.Rossini -"Rmi-IIOP I e II parte",Mokabyte N.44 Settembre 2000 e successivo
[3] G.Puliti - "Enterprise Java Beans", Mokabyte N.41, Maggio 2000
[4] A.Giovannini, R.Spazzoli - Rassegna di application server EJB, Mokabyte N.44, Settembre 2000
[5] A.Giovannini, EJB Platform, Mokabyte N.45, Ottobre 2000
[6] J2SDKEE1.2 Documentation
[7] Enterprise JavaBeans – Fundamentals Short Course 

Chi volesse mettersi in contatto con la redazione può farlo scrivendo a mokainfo@mokabyte.it