MokaByte Numero  45  - Ottobre 2000
 
EJB Platform
di 
Andrea Giovannini
introduzione alla tecnologia degli Enterprise Java Beans

Lo sviluppo di applicazione distribuite ha visto negli ultimi tempi il passaggio dalle architetture client/server ai sistemi three-tier con client Web. Per quanto riguarda lo sviluppo del middle tier, quello che incorpora la logica applicativa, ci si basa sempre di più su componenti riusabili ai quali richiedere servizi esposti come metodi di un oggetto. 
Introduzione
I più noti modelli di programmazione a oggetti distribuiti sono 
  • CORBA: è una specifica per l'interoperabilità di oggetti remoti mediante un bus software (ORB - Object Request Broker). Ultimamente è stato proposto un modello di componenti chiamato Corba Component Model (CCM). E' importante osservare che un oggetto CORBA può essere scritto in qualsiasi linguaggio per il quale sia previsto un binding come ad esempio C++ e Java. Il protocollo di trasporto utilizzato è IIOP (Internet Inter-ORB Protocol);
  • Java RMI: il modello di programmazione distribuita fornito da Java è ormai consolidato e fornisce una solida base per lo sviluppo di applicazioni multipiattaforma in Java che è l'unico linguaggio per cui RMI è disponibile. Una delle novità più importanti introdotte ultimemente con RMI ([6]) è data dalla possibilità di utilizzare IIOP come protocollo di trasporto: in questo modo un qualsiasi client CORBA può accedere ai servizi esposti da un oggetto sviluppato con il più semplice modello Java/RMI;
  • COM+: il modello di componenti proposto da Microsoft si basa sull'utilizzo di componenti che offrono una serie di servizi definiti in un'apposita interfaccia. Un'interfaccia COM definisce il formato in memoria dell'oggetto server che può essere sviluppato nei linguaggi per i quali esiste l'apposito mapping, ovvero prevalentemente C++, Visual Basic e C# ma anche produttori terze parti possono fornire il supporto COM+ per altri linguaggi. COM+ è alla base dalla piattaforma .NET presentata negli ultimi mesi da Microsoft.


La soluzione proposta da Sun prende il nome di J2EE, Java 2 Enterprise Edition ([1]). La piattaforma J2EE è uno standard aperto per lo sviluppo di applicazioni enterprise con il linguaggio Java e comprende diverse tecnologie come

  • Transazioni - JTA
  • Servizi di naming - JNDI
  • Accesso a database - JDBC
  • Messaging asincrono - JMS
  • Programmazione per componenti - EJB
In questo articolo approfondiremo la programmazione degli Enterprise Java Beans esaminando anche le novità introdotte con la versione 2.0 delle specifiche.
 
 
 

Enterprise Java Beans
EJB non è un prodotto ma una specifica per lo sviluppo di applicazioni enterprise, cioè applicazioni basate su componenti distribuiti e transazionali. Più precisamente Sun fornisce

  • un modello di programmazione basato su componenti server-side;
  • una API vendor-neutral che definisce le interfacce del modello, ovvero il contratto che i componenti sviluppati devono rispettare per garantire la portabilità fra application server diversi.
L'architettura di base EJB è basata su
  • un server EJB
  • i container
  • i componenti
  • client EJB
  • servizi ausiliari (JTS, JMS, ...)
Approfondiamo ora in dettaglio la tecnologia EJB che rappresenta il nucleo della piattaforma J2EE.

EJB Server
Il server EJB fornisce l'ambiente di esecuzione dei container, li rende visibili all'esterno e rende loro disponibili vari servizi come ad esempio il bilanciamento del carico.

Container
I container rappresentano l'ambiente di esecuzione degli EJB e forniscono ai bean i servizi di base (dipendenti dall'implementazione) attraverso la API standard definita nelle specifiche EJB. Il client di una applicazione EJB non accede mai direttamente al componente perchè le sue chiamate vengono intercettate dai container. Questo concetto di interception è molto importante nella programmazione distribuita ed è alla base anche di altri modelli come ad esempio COM+ di Microsoft. Il container in questo modo è in grado di gestire il ciclo di vita dei componenti, le transazioni, la sicurezza, gli aspetti di persistenza e vari altri servizi in modo trasparente ai client e allo sviluppatore di un EJB. Quest'ultimo può quindi concentrarsi sull'implementazione della logica applicativa dei propri componenti.

Per ottimizzare le prestazioni un EJB container gestisce un pool di risorse in cui 'riporre' i bean non utilizzati. Il riferimento remoto rimane comunque attivo e a una seguente invocazione del client il container provvede a recuperare il bean dal pool.

L'interazione fra un EJB e il suo container può avvenire attraverso metodi callback, l'oggetto EJBContext o JNDI.

  • Ogni EJB deve implementare una apposita interfaccia (che estende EnterpriseBean) la quale definisce vari metodi callback che comunicano al bean il verificarsi di determinati eventi come attivazione, persistenza e terminazione di una transazione;
  • ogni EJB mantiene inoltre un riferimento al container mediante l'interfaccia EJBContext; il bean può quindi ad esempio richiedere informazioni sullo stato della transazione;
  • JNDI è un'interfaccia a servizi di naming e attraverso JNDI un bean può accedere a varie risorse come ad esempio servizi JMS o altri EJB.
Componenti
Un componente EJB è costituito da diversi file:
  • Remote interface: definisce tutti i metodi applicativi del componente ed estende l'interfaccia EJBObject. Si osservi che lo sviluppatore non deve implementare questa interfaccia: saranno infatti i tool a disposizione del particolare EJB server che genereranno il codice necessario per intercettare le chiamate del client e redirigerle sul componente. Supponiamo di voler realizzare un'applicazione per la gestione di libri: la remote interface avrà il seguente codice
  • import javax.ejb.EJBObject;
    import javax.rmi.RMIException;
    
    public interface Book extends EJBObject {
      public String getCode() throws RemoteException;
      public String getAuthor() throws RemoteException;
      public void setAuthor(String author) 
                            throws RemoteException;
      public String getTitle() throws RemoteException;
      public void setTitle(String title)
                           throws RemoteException;
    }
    L'interfaccia Book definisce metodi per accedere e modificare i dati che rappresentano un libro e che saranno memorizzati su un'apposita tabella su database. Come vedremo in seguito questi sono i tipici metodi di un particolare tipo di EJB, gli entity bean, che rappresenta un oggetto memorizzato su database.

    Osserviamo come il codice precedente non definisca metodi applicativi; questi vengono infatti implementati da un diverso tipo di EJB, i session bean, e saranno definiti in una remote interface distinta. Supponiamo ad esempio di voler implementare la gestione di ordini di libri on-line: questa attività sarà realizzata attraverso un session bean che chiameremo OrderAgent e che avrà la seguente remote interface

    import javax.ejb.EJBObject;
    import javax.rmi.RMIException;
    
    public interface OrderAgent extends EJBObject {
       public void placeOrder(Book book, Customer customer)
                              throws RemoteException;
    }
    Il metodo placeOrder() esegue l'inserimento di un ordine per un libro da parte di un cliente (le cui informazioni sono rappresentate dal componente Customer) coinvolgendo tutti gli entity bean necessari.
     
  • Home interface: questa interfaccia, che estende EJBHome, definisce dei metody factory per creare un componente e per recuperare uno o più EJB in base a determinati criteri; ad esempio la home interface per il nostro componente Book sarà così definita
  • import javax.ejb.*;
    import java.util.Collection;
    import java.rmi.RemoteException;
    
    public interface BookHome extends EJBHome {
      public Book create(String code) 
                throws CreateException, RemoteException;
      public Book findByPrimaryKey(String code)
                throws FinderException, RemoteException;
      public Collection findByAuthor(String author)
                throws FinderException, RemoteException;
    }
    I metodi findBy della home interface permettono di recuperare un EJB o un'intera collezione in base a vari criteri di selezione: findByPrimaryKey() restituisce l'EJB corrispondente alla chiave primaria specificata mentre findByAuthor() restituisce una collezione di EJB di libri scritti dall'autore specificato. Se non è possibile trovare i bean richiesti viene sollevata una FinderException.
     
  • EJB: il componente vero e proprio che implementa la logica applicativa definita nella remote interface. Fra breve vedremo i vari tipi di EJB ed alcuni esempi di codice;

  •  
  • qualsiasi altro file o risorsa necessario, come ad esempio i file per la definizione delle eccezioni o le classi che rappresentano la chiave primaria degli entity bean;

  •  
  • un file, chiamato deployment descriptor, che contiene le proprietà del componente e che viene redatto in formato XML;
Come accennato in precedenza sono previsti tre tipi di EJB: entity bean, session bean e message driven bean. Ognuno di essi permette di modellare un determinato aspetto dell'applicazione, rispettivamente l'accesso ai dati, l'esecuzione di attività/processi e l'interazione con servizi di messagging asincrono. Vediamoli ora in dettaglio.

Gli entity bean implementano l'interfaccia EntityBean e modellano i dati presenti su database. Lo sviluppatore non deve quindi scrivere codice SQL per accedere o modificare i dati ma userà i metodi della remote interface. Vediamo come implementare il bean per il componente Book

import javax.ejb.*;

public class BookEJB implements EntityBean {

        private String code;
        private String author;
        private String title;

        private EntityContext context;

        // business methods

        public String getCode() throws RemoteException {
                return code;
        }
        public String getAuthor() throws RemoteException {
                return author;
        }
        public void setAuthor(String author)
                              throws RemoteException {
                this.author = author;
        }
        public String getTitle() throws RemoteException {
                return title;
        }
        public void setTitle(String title)
                             throws RemoteException {
                this.title = title;
        }

        // EntityContext handling
        public void setEntityContext(EntityContext context) {
                this.context = context;
        }

        public void unsetEntityContext() {
                this.context = null;
        }
        // callback methods

        public Integer ejbCreate(String code) {
                // using container managed persistence
                this.code = code;
                return null; 
        }

        public void ejbPostCreate(String code) {}

        public void ejbLoad() {}
        public void ejbStore() {}
        public void ejbActivate() {}
        public void ejbPassivate() {}
        public void ejbRemove() {}

}
Il codice precedente presenta i metodi applicativi definiti in precedenza nella remote interface. Osserviamo però che la classe non implementa l'interfaccia Book perchè l'accesso a tali metodi è infatti mediato dal container. I metodi setEntityContext() e unsetEntityContext() permettono di impostare l'oggetto EntityContext che, come visto in precedenza, rappresenta un riferimento al container. Seguono infine alcuni metodi callback invocati dal container al verificarsi di determinati eventi durante il ciclo di vita del bean.

Le gestione della persistenza per gli entity bean può essere di due tipi:

  • container-managed persistence: la persistenza del bean su database viene gestita automaticamente dal container e le informazioni relative a quali campi serializzare su database vengono mantenute nel deployment-descriptor del bean;
  • bean-managed persistence: in questo caso il bean contiene il codice per accedere a database e gestire la coerenza dei dati.
I session bean, a differenza degli entity bean, non rappresentano dati ma modellano attività e possono essere stateless o stateful. I bean stateless non mantengono alcuna informazione relativa al loro stato e possono quindi essere gestiti in pool per ottimizzare le prestazioni. I bean stateful mantengono invece informazioni relative alla sessione client ma non possono comunque sopravvivere a un crash di sistema. vediamo una possibile implementazione di OrderAgent:
import javax.ejb.SessionBean;

public class OrderAgentEJB implements SessionBean {
  public void placeOrder(Book book, Customer customer) 
                         throws RemoteException {
    Order order;
    OrderHome orderHome;

    orderHome = ... // home reference
    order = orderHome.create(book.getCode(),
                             customer.getId());
  } 
}
Il codice precedente, anche se molto semplificato, mostra comunque la possibilità di poter distinguere componenti basati sui dati e componenti basati su task.

Una delle maggiori novità introdotte con la nuova versione 2.0 delle specifiche EJB riguarda l'introduzione di un nuovo tipo di bean, i message driven bean, che si occupano di gestire i messaggi JMS. JMS è una API vendor-neutral per la gestione di sistemi di messaging asincrono. Si veda [3] per una introduzione a JMS. Prima della versione 2.0 degli EJB questi potevano accedere a servizi JMS attraverso JNDI; ora è possibile sviluppare client JMS che vengono eseguiti in un ambiente robusto e affidabile quale un container EJB. Vediamo uno scheletro di codice di un message driven bean.

import javax.ejb.*;
import javax.jms.*;

public class MessageHandlerEJB implements MessageDrivenBean {
        public void onMessage(Message message) {
                // handle message
                // ...
        }
}
Quando si esegue il deploy di un message driven bean si assegna ad esso quali tipi di messaggio devono essergli instradati. E' inoltre importante osservare che i messaggi non devono essere necessariamente generati da un altro EJB ma anche ad esempio da un'applicazione legacy che pubblica un messaggio in un prodotto compatibile con JMS.

Osserviamo che per garantire la massima integrazione fra applicazioni è possibile accedere ad un EJB come oggetto CORBA.
 
 
 

EJB 2.0
Le novità principali introdotte con la nuova versione 2.0 delle specifiche, oltre all'introduzione dei message driven bean, riguardano la container-managed persistence. Come accennato in precedenza è possibile configurare un EJB in modo che il container si faccia carico di mappare i suoi attributi in colonne di una tabella su database. Il modello introdotto con la versione 2.0 rende questo meccanismo molto più potente introducendo un nuovo componente all'architettura, il persistence manager, che incapsula le funzionalità di persistenza mentre il container mantiene la gestione si sicurezza, transazioni e risorse.

Il persistence manager è in grado di generare il mapping di entity bean verso database relazionali in base alle informazioni contenute nel descrittore del bean, come in EJB 1.1, ma sono state introdotte diverse novità

  • poiché il persistence manager è un componente separato dal container è possibile sviluppare manager custom non necessariamente legati a un database ma ad esempio ad un sistema ERP;
  • il mapping degli attributi del bean avviene in modo diverso e ora consente di gestire attributi di tipo non primitivo (i cosiddetti dependant object) con i quali il bean può avere relazioni del tipo 1-1 o 1 a molti;
  • è possibile modellare relazioni con altri EJB aventi cardinalità 1-1, 1 a molti o molti a molti;
  • la modalità di implementazione di un entity bean è diversa e non è necessario elencare i campi persistenti. Ad esempio il codice del componente Book diventa
  • import javax.ejb.*;
    
    public abstract BookEJB implements EntityBean {
      // instance fields
      private EntityContext context;
    
      // business methods
      public abstract String getCode() throws RemoteException;
      public abstract String getAuthor() throws RemoteException;
      public abstract void setAuthor(String author) 
                           throws RemoteException;
    
            // ...
    }
    Possiamo osservare come nessuno degli attributi persistenti venga elencato nella classe: questi verranno specificati nel deployment descriptor e il persistence manager genererà la classe concreta corrispondente;
  • quando abbiamo introdotto la home interface degli EJB abbiamo visto anche i metodi find. Con gli EJB 2.0 viene introdotto EJB Query Language (EJB QL) che permette di specificare come il persistence manager deve implementare i metodi find;
  • la home interface degli entity bean può ora includere metodi applicativi, chiamati metodi ejbHome.
Per maggiori informazioni è possibile consultare, oltre alla documentazione ufficiale Sun, l'articolo in [4].
 
 
 

Conclusioni
In questo articolo abbiamo visto in modo dettagliato i punti chiave della tecnologia J2EE. I vari esempi di codice presentati ci hanno permesso di comprendere come lo sviluppo di applicazioni modellate come EJB possa semplificare lo sviluppo stesso grazie all'architettura delle applicazioni J2EE e ai vantaggi che derivano dall'utilizzo di un linguaggio object-oriented come Java. Vorrei infine ringraziare Raffaele Spazzoli per il supporto avuto nella realizzazione di questo articolo.
 
 
 

Riferimenti

  1. Documentazione ufficiale Sun, http://java.sun.com/j2ee
  2. Speciale Componenti - Computer Programming, n. 91, Maggio 2000
  3. Gordon Van Huizen, JMS: An infrastructure for XML-based business-to-business communication, JavaWorld, http://www.javaworld.com/jw-02-2000/jw-02-jmsxml.html
  4. Richard Monsol-Haefel, Read all about EJB 2.0, JavaWorld, http://www.javaworld.com/jw-06-2000/jw-0609-ejb.html
  5. Gopalan Suresh Raj - Articoli e tutorial - http://www.execpc.com/~gopalan/java/ejb.html
  6. Akira Andoh, Simon Nash - RMI over IIOP - http://www.javaworld.com/jw-12-1999/jw-12-iiop.html
Chi volesse mettersi in contatto con la redazione può farlo scrivendo a mokainfo@mokabyte.it