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