MokaByte Numero 45 - Ottobre 2000
 
Gli eventi di Jini
di 
Raffaele Spazzoli
Analisi del modello degli eventi distribuiti di Jini

In questo articolo viene descritto il modello di programmazione ad eventi distribuiti proposto per Jini. Vengono poi messe in evidenza le differenze col modello ad eventi locale usato nei Java Bean. Infine viene proposto un piccolo framework che agisce da ponte fra i due modelli

Benvenuti al secondo articolo della serie sulle tecnologie di Jini. Nel precedente articolo avevamo visto come usare i lease, in questo ci occuperemo degli eventi distribuiti.
La programmazione ad eventi è diventata una pratica sempre più comune negli ultimi tempi, soprattutto nella programmazione delle interfacce grafiche. In questo stile di programmazione un oggetto registra il proprio interesse a ricevere notifiche di eventi che accadono al di fuori di esso; in seguito la notifica dell’evento avviene invocando un particolare metodo (callback) dell’oggetto che si è registrato.
In Jini e nella programmazione distribuita in generale può essere utile avvalersi di questo modello di programmazione. La scrittura di oggetti sensibili ad eventi in ambiente distribuito presenta però problemi che non esistono se si opera in un singolo address space. In un singolo address space o all’interno di una sola JVM visto che parliamo di Java, si può assumere che la consegna degli eventi sia molto veloce, ordinata ed affidabile. Tali assunzioni non possono essere considerate vere in ambiente distribuito, dove le notifiche possono arrivare in maniera disordinata o non arrivare affatto.
Inoltre in ambiente distribuito possono nascere anche esigenze diverse da quelle presenti in ambiente locale, può darsi per esempio che un oggetto desideri ricevere gli eventi cui è interessato ad intervalli di tempo regolari, oppure se si pensa ad un oggetto attivabile RMI, è ragionevole pensare che un evento ad esso indirizzato non debba essere consegnato se l’oggetto è dormiente, ma la consegna debba essere posticipata fino al risveglio dell’oggetto. Per ottenere questi effetti è necessario che sia possibile disaccoppiare facilmente il produttore dell’evento dai vari consumatori.
 
 
 

Il modello ad eventi distribuiti di Jini
Il modello ad eventi distribuiti di Jini è composto da tre attori:

  • L’oggetto che registra l’interesse per un certo evento.
  • L’oggetto nel quale avviene l’evento (detto generator)
  • L’oggetto che riceve le notifiche dell’evento (detto listener)

 
Figura 1 Relazioni fra gli oggetti che compongono il modello ad eventi distribuito. Il listener viene passato come argomento al momento della registrazione assieme al MarshalledObject, si noti che nel modello non è stata definita l’interfaccia per la registrazione dei listener. Il generator chiama il metodo notify() dei suoi listener ogni volta che si verifica un evento. E’ compito dell’oggetto che effettua la registrazione occuparsi del lease che viene restituito nell’oggetto EventRegistration.

In figura 1 vengono mostrate le relazioni fra questi oggetti.
In Jini l’oggetto che registra l’interesse ad un certo evento non necessariamente è lo stesso che ne riceve le notifiche; la registrazione però è legata ad un lease che deve essere mantenuto valido per tutta la durata dell’interesse all’evento.
Il generator è colui che genera gli eventi ed ha il compito di notificarli ai vari listener registrati.
Il listener riceve la notifica dell’evento mediante l’invocazione del metodo notify().
Il listener deve implementare la seguente interfaccia (che troviamo nel package net.Jini.core.event):

public interface RemoteEventListener extends Remote,java.util.EventListener{
void notify(RemoteEvent theEvent) throws UnknownEventException, RemoteException;

L’interfaccia estende l’interfaccia Remote e dunque è predisposta per essere esposta da oggetti remoti. Il metodo notify() verrà chiamato dal generator a fronte dell’evento. L’esecuzione del metodo notify() dovrebbe essere piuttosto rapida in modo tale da liberare il generator che è bloccato sulla chiamata (in RMI tutte le chiamate sono bloccanti, anche quelle che ritornano void); in caso sia necessario una lunga elaborazione il modo migliore è quello di demandare l’invocazione dei veri metodi notify() ad un thread separato.
L’unico parametro del metodo notify è di tipo RemoteEvent, tale classe è così definita:

public class RemoteEvent extends java.util.EventObject {
public RemoteEvent(Object source,long eventID,long seqNum,MarshalledObject handback)
public Object getSource();
public long getID();
public long getSequenceNumber();
public MarshalledObject getRegistrationObject();
}

RemoteEvent ha un singolo costruttore con quattro parametri:
Il primo di tipo Object è la sorgente dell’evento: il generator. Il secondo è l’identificativo dell’evento. La combinazione della sorgente dell’evento più l’identificativo devono fornire sufficienti informazioni per capire il tipo dell’evento. Infatti il listener riceve tramite notify() sempre un oggetto di tipo RemoteEvent, una volta dedotto il tipo dell’evento si può fare un typecast al tipo reale del parametro passato nel notify.
Il terzo parametro è il numero di sequenza dell’evento. Un generator deve garantire che per un certo tipo di evento tale numero sia sempre crescente. Questo serve dalla parte del client per ordinare correttamente gli eventi. 
Il quarto parametro è di tipo MarshalledObject, esso è un parametro che deve essere passato al momento della registrazione di interesse ad un certo evento e deve essere ripassato al listener ogni volta che l’evento si verifica. Tale parametro può contenere qualsiasi cosa in particolare esso potrebbe contenere l’azione (closure) da svolgere a fronte dell’evento da parte del listener, azione che potrebbe essere nota solo a chi chiede la registrazione dell’evento.
La Sun ci mette a disposizione anche una classe di utilità che non fa parte delle specifiche del modello, ma può risultare comoda. La classe si chiama EventRegistration e dovrebbe essere ritornata a fronte di una registrazione di interesse ad un evento; essa è così definita: 

public class EventRegistration implements java.io.Serializable {
public EventRegistration(long eventID,Object eventSource,Lease eventLease,long seqNum);
public long getID();
public Object getSource();
public Lease getLease();
public long getSequenceNumber();
}

Si noti che un’istanza della classe EventRegistration contiene il lease associato alla registrazione che dovrà essere mantenuto valido se si vogliono ricevere le notifiche dell’evento. Inoltre essa contiene anche il sequence number che servirà al listener per sincronizzarsi sugli eventi da ricevere. In pratica se dopo la registrazione il listener riceve un evento con sequence number inferiore a quello della registrazione, il listener non deve tenere conto di tale evento.
 
 
 

Applicazioni del modello ad eventi distribuito
Una delle principali ragioni che hanno portato a progettare il modello appena presentato è stata la possibilità di avere agenti distribuiti che potessero migliorare la qualità del servizio degli eventi distribuiti. Tipicamente tali oggetti si interpongono tra il generator e il listener e svolgono un qualche tipo di filtro. Vediamone qualche esempio fra quelli proposti dalla Sun: il primo detto store and forward agent è un oggetto in grado di ricevere varie notifiche di eventi, memorizzarle e infine spedirle ai reali destinatari sulla base di una politica temporale parametrizzabile. Inoltre lo store and forward agent potrebbe implementare una politica di affidabilità della consegna dell’evento molto accurata, magari predisponendosi a veri ritentativi di notifica nel caso alcune consegne di eventi dovessero fallire.
Un altro esempio è il Notification Mailbox. Lo scopo di questo oggetto è memorizzare le notifiche di eventi in luogo del reale destinatario. Sarà poi il listener a richiedere la notifica degli eventi quando è disposto a riceverli. Il Notification Mailbox trova la sua normale applicazione con oggetti RMI attivabili i quali tipicamente non desiderano ricevere eventi quando sono dormienti, ma non appena entrano in esecuzione per altre ragioni, possono essere notificati di tutti gli eventi ai quali erano interessati.
Si noti che la capacità di inserire agenti fra il generator e il listener è dovuta al fatto che l’interfaccia del listener è immutabile dunque per il generator non fa differenza inviare l’evento a un agente o a un vero listener, e inoltre è possibile progettare agenti realmente generici. Come vedremo questa è forse la maggiore differenza che distingue il modello ad eventi distribuito da quello locale.
 
 
 

Similitudini e differenze col modello ad eventi locale
Come sappiamo Java possiede anche un modello programmazione ad eventi locale; più che un modello è un vero e proprio pattern di programmazione che si deve usare quando si progettano JavaBean. In breve il pattern si riassume come segue: dato evento di un certo tipo di evento xxx si definisce la classe xxxEvent ereditandola da una delle classi della gerarchia che ha come padre java.util.EventObject; si costruisce la classe creata in modo che possa contenere tutte le informazioni necessarie a decifrare il tipo di evento accaduto. Poi si definisce una interfaccia dal nome xxxListener che espone solo metodi con valore di ritorno void e che accettano un solo parametro di tipo xxxEvent, cioè metodi del tipo: public void something(xxxEvent theEvent). Ogni metodo corrisponde ad un particolare sottocaso dell’evento (per esempio nel caso del MouseEvent abbiamo fra gli altri i metodi mousePressed() e mouseReleased() per distinguere fra i due casi di bottone premuto e rilasciato). Ultimo elemento del pattern è che le sorgenti degli eventi devono dare la possibilità di registrare l’interesse a ricevere la notifica degli eventi definendo due metodi col nome addxxxEventListener(xxxListener theListener) e removexxxEventListener(xxxListener theListener) (il secondo serve ovviamente a cancellare la registrazione).
Le similitudini fra i due modelli sono le seguenti:

  • La propagazione degli eventi avviene mediante l’invocazione di metodi callback(locali in un caso remoti nell’altro)
  • La descrizione dell’evento viene passato in un unico parametro il cui tipo eredita in qualche modo da java.util.EventObject
  • Il listener dell’evento deve esporre un’interfaccia che eredita in qualche modo da java.util.EventListener.
Vi sono però anche delle differenze:
  • L’identificazione del tipo dell’evento nel modello distribuito avviene mediante l’identificatore dell’evento e la sorgente che l’ha generato. Nel modello locale l’identificazione avviene in parte grazie al tipo dell’evento in parte dipende dal particolare metodo invocato nell’interfaccia del listener.
  • Le notifiche nel modello distribuito avvengono mediante l’invocazione di un solo metodo (notify()).
  • La registrazione dell’interesse ad un evento nel modello distribuito è limitata nel tempo (anche se rinnovabile) mentre nel modello locale rimane valida fino a che non sia esplicitamente cancellata.
  • Infine nel modello distribuito colui che registra l’interesse ad un evento può passare come parte della registrazione un parametro che verrà passato al listener dal generator ogni volta che si verifica l’evento.
Potrebbe essere utile creare JavaBean capaci di ascoltare eventi remoti provenienti da JavaBean attivi su macchine separate. E’ necessario creare un ponte che renda in qualche modo compatibili i due modelli ad eventi. Questo ponte deve avere le seguenti caratteristiche:
  • Deve essere possibile che un listener di tipo remoto trasformi le notifiche per un listener di tipo locale, invocando dunque il corretto metodo locale a fronte di un evento.
  • Deve essere possibile creare un oggetto di tipo RemoteEvent da un evento locale senza perdita di informazioni.
  • Deve essere possibile creare un evento locale da un oggetto di tipo RemoteEvent senza perdita di informazioni.
E’ possibile raggiungere questi risultati in svariati modi, nel prossimo paragrafo presenterò una possibile soluzione.
 
 
 

Un esempio di traslazione fra i due modelli ad eventi
L’obiettivo di base di questo esempio è fare si che si possa concepire i programmi usando un solo modello, quello locale che è più elegante, anche quando i JavaBean che si vogliono far comunicare non appartengano alla stessa JVM. In questo esempio non ci si preoccupa di problemi di efficienza, bisogna però fare attenzione perché i genere in ambiente locale si tende a scatenare una gran quantità di eventi, se questi vengono trasmessi in rete, magari anche a più listener si può avere il rischio di un degrado delle prestazioni.
Per risolvere il problema della traslazione fra i due modelli ad eventi ho costruito in piccolo framework (contenuto nel package foele.jini.event) costituito sostanzialmente da due attori: un RemoteBeanEventFirer ed un RemoteBeanEventListener. Il RemoteBeanEventFirer si registra come listener di certi eventi presso un JavaBean locale e suo compito è di notificare gli eventi ai RemoteBeanEventListener che si sono registrati presso di lui. Inoltre il RemoteBeanEventFirer deve anche gestire i lease relativi alla registrazione dei  RemoteBeanEventListener. 
Si è definita una implementazione dell’interfaccia RemoteBeanEventFirer (AstractRemoteBeanEventFirer) che gestisce i lease e definisce il metodo:
protected final void fireEvent(java.lang.reflect.Method Method, java.util.EventObject Event).
L’idea è questa ogni volta che si necessita di un listener per un particolare tipo di eventi si eredita da AbstractRemoteBeanEventFirer una classe che implementi l’interfaccia del listener in questione, all’interno dei metodi di tale interfaccia non bisogna fare altro che costruire un oggetto di tipo Method che rispecchi il metodo chiamato e poi chiamare il metodo fireEvent passando come parametri l’oggetto Method creato e l’oggetto di tipo evento che il metodo chiamato deve avere come parametro. Ad esempio supponiamo di voler definire un RemoteActionEventFirer non dovremo fare altro che scrivere il seguente codice:

public class RemoteActionEventFirer extends foele.jini.event.AbstractRemoteBeanEventFirer implements java.awt.event.ActionListener {

public void actionPerformed(java.awt.event.ActionEvent AE) {
 Class cl=java.awt.event.ActionListener.class;
 Class[] argsTypes={java.awt.event.ActionEvent.class};
java.lang.reflect.Method Method=cl.getDeclaredMethod("actionPerformed",argsTypes);
 fireEvent(Method,AE);

}

Questo è l’unico sforzo di implementazione che si richiede a chi voglia usare questo framework, come si può vedere è piuttosto semplice da applicare in quanto il codice da inserire nella definizione dei metodi dei vari listener è praticamente sempre lo stesso. Una volta che abbiamo RemoteBeanEventFirer lo dobbiamo registrare come listener al bean di cui ci interessa catturare gli eventi.
Vediamo ora cosa accade dalla parte del RemoteBeanEventListener. Questa classe implementa l’interfaccia RemoteEventListener, tramite la quale riceverà le notifiche degli eventi da parte del RemoteBeanEventFirer. Inoltre si occupa del rinnovo del lease (delegando in realtà il compito ad un LeaseRenewalManager).
Nel costruttore questa classe riceve due argomenti: il primo è il RemoteBeanEventFirer a cui registrarsi, il secondo è il tipo di listener per i quali svolgerà la funzione di ripetitore. E’ possibile registrare tali listener mediante il metodo:
public synchronized void addListener(java.util.EventListener Listener) throws java.lang.ClassCastException. Si noti che tale metodo lancia una eccezione se il listener che si tenta di registrare non è dello stesso tipo di quello dichiarato al momento della creazione. Una volta che abbiamo un oggetto di tipo RemoteBeanEventListener possiamo registrare tutti i listener che vogliamo e questi riceveranno gli eventi scatenati dal bean remoto. La figura 2 illustra ulteriormente le relazioni fra le classi di questo piccolo framework.
 
 
 

Figura 2 - Relazioni fra le classi che compongono il framework di traduzione dal modello ad eventi distribuito e quello locale

 

Conclusioni 
Il fatto che i progettisti di Jini abbiano deciso di definire un modello ad eventi in ambiente distribuito è sicuramente una cosa positiva anche perché effettivamente se ne sentiva la mancanza. Il modello in sé ricorda da vicino il vecchio modello ad eventi del JDK1.0 in cui in un unico metodo handleEvent() (in questo caso notify()) bisognava gestire tutti i possibili eventi. Questa volta la scelta è stata giustificata più a fondo e non credo che dovremo aspettarci grossi cambiamenti in tempi brevi come avvenne nel passaggio da JDK1.0 a JDK1.1. Il modello di programmazione ad eventi trasportato in ambiente distribuito apre la strada a nuovi tipi di modelli di interazione fra oggetti remoti, inoltre la semplicità di implementazione che caratterizza questo modello, come del resto la maggior parte delle tecnologie Java, di sicuro accelererà i tempi di sviluppo.

Al solito gli esempi possono essere scaricati qui
 
 
 

Bibliografia
[1] "Jini Tecnhology Core Platform Specification", Sun Microsystems, 2000
[2] "Jini API Documentation ", Sun Microsystems, 2000
[3] " A Collection of Jini™ Technology Helper Utilities and Services Specifications", Sun Microsystems, 2000
I documenti sono scaricabili al sito http://www.sun.com/products/jini.
 
 
 


Raffaele Spazzoli è laureato in ingegneria informatica. Da anni coltiva la propria passione per Java studiando e testando le soluzioni tecnologiche introdotte dalla Sun. Può essere contattato tramite e-mail all’indirizzo RaffaeleSpazzoli@mailandnews.com.

 

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