Eventi Bean
La
notifica del cambiamento di valore delle proprietà
bound è un meccanismo di comunicazione tra
Beans. Se si vuole che un Bean sia in grado di propagare
eventi di tipo più generico, o comunque eventi
che non sia comodo rappresentare come un cambiamenti
di stato, è possibile utilizzare un meccanismo
di eventi generico, del tutto simile a quello presente
nei componenti grafici Swing ed AWT. I prossimi paragrafi
servono ad illustrare le tre fasi dell'implementazione:
creazione dell'Evento, definizione dell'ascoltatore
e infine la creazione della sorgente di eventi.
Creazione
di un Evento
Per implementare un meccanismo di comunicazione basato
su eventi, occorre anzitutto definire un opportuna
sottoclasse di EventObject, che racchiuda tutte le
informazioni relative all'evento da propagare:
public
class <EventType> extends EventObject {
private <ParamType> param
public <EventType>(Object source,<ParamType>
param) {
super(source);
this.param = param;
}
public <ParamType> getParameter()
{
return param;
}
}
La
principale variazione sul tema si ha sul numero e
sul tipo di parametri: tanto più complesso
è l'evento da descrivere, maggiori saranno
i parametri in gioco. L'unico parametro che è
obbligatorio fornire è un reference all'oggetto
che ha generato l'evento: tale reference, richiamabile
con il metodo getSource() della classe EventObject,
permetterà all'ascoltatore di interrogare la
sorgente degli eventi, qualora ce ne fosse bisogno.
Destinatari
di Eventi
Il secondo passaggio è quello di definire l'interfaccia
di programmazione degli ascoltatori di eventi. Tale
interfaccia deve essere definita come sottoclasse
di EventListener, per essere riconoscibile come ascoltatore
dall'Introspector. Lo schema di sviluppo degli ascoltatori
segue lo schema:
import
java.awt.event.*;
public
Interface <EventListener> extends EventListener
{
public void <EventType>Performed(<EventType>
e);
}
Le
convenzioni di naming dei metodi dell'interfaccia
non seguono uno schema standard:: la convenzione descritta
nell'esempio, <EventType>performed, può
essere seguita o meno. L'importante è che il
nome dei metodi dell'interfaccia Listener suggeriscano
il tipo di azione sottostante, e che accettino come
parametro un Evento del tipo giusto.
Sorgenti
di Eventi
Se vogliamo aggiungere ad un Bean la capacità
di generare eventi, dobbiamo implementare una coppia
di metodi:
add<EventListenerType>(<EventListenerType>
l)
remove<EventListenerType>(<EventListenerType>
l)
La
gestione della lista degli ascoltatori e l'invio degli
eventi segue una formula standard, descritta nelle
righe seguenti:
private
Vector listeners = new Vector();
public void add<EventListenerType>(<EventListenerType>
l) {
listeners.add(l);
}
public void remove<EventListenerType>(<EventListenerType>
l) {
listeners.remove(l);
}
protected void fire<Eventype>(<EvenType>
e) {
Enumeration listenersEnumeration = listeners.elements();
while(listenersEnumeration.hasMoreElements())
{
<EventListenerType>
listener=(<EventListenerType>)
listenersEnumeration.nextElement();
listener.<EventType>Performed(e);
}
}
Sorgenti
unicast
In
alcuni casi occorre definire sorgenti di eventi capaci
di servire un unico ascoltatore. Per implementare
tali classi, che fungono da sorgenti unicast, si può
seguire il seguente modello
private
<EventListenerType> listener;
public void add<EventListenerType>(<EventListenerType>
l)
throws TooManyListenersException {
if(listener==null)
listener=l;
else
throw new TooManyListenerException();
}
public void remove<EventListenerType>(<EventListenerType>
l) {
listener=null;
}
protected void fire<Eventype>(<EvenType>
e) {
if(listener!=null)
listener.<EventType>Performed(e);
}
Ascoltatori
di Eventi: Event Adapter
Se
vogliamo che un evento generato da un Bean scateni
un'azione su un altro Bean, dobbiamo creare un oggetto
che realizzi un collegamento tra i due. Tale classe,
detta Adapter, viene registrata come ascoltatore presso
la sorgente dell'Evento, e formula una chiamata al
metodo destinazione ogni volta che riceve una notifica
dal Bean sorgente.
Figura 5 - Un Adapter funge da ponte di collegamento
tra gli Eventi di un Bean e i metodi di un'altro
Gli
strumenti grafici tipo JBuilder generano questo tipo
di classi in maniera automatica: tutto quello che
l'utente deve fare è collegare, con pochi click
di mouse, l'Evento di un Bean sorgente ad un metodo
di un Bean Target. Qui di seguito vediamo il codice
di un Adapter, generato automaticamente dal Bean Box,
che collega la pressione di un pulsante al metodo
startJuggling(ActionEvent e) del Bean Juggler:
//
Automatically generated event hookup file.
public
class ___Hookup_172935aa26 implements java.awt.event.ActionListener,
java.io.Serializable {
public void setTarget(sunw.demo.juggler.Juggler t)
{
target = t;
}
public void actionPerformed(java.awt.event.ActionEvent
arg0) {
target.startJuggling(arg0);
}
private sunw.demo.juggler.Juggler target;
}
Un
esempio di Bean con Eventi
Il
prossimo esempio è un Bean Timer, che ha il
compito di generare battiti di orologio ad intervalli
regolari. Questo Componente è un tipico esempio
di Bean non grafico.
La
prima classe che andremo a definire è quella
che implementa il tipo di Evento:
package
com.mokabyte.mokabook.javaBeans.timer;
import
com.mokabyte.mokabook.javaBeans.*;
import java.io.*;
import java.util.*;
public
class TimerEvent extends EventObject implements Serializable{
public
TimerEvent(Object source) {
super(source);
}
}
Come
si può vedere, l'implementazione di un nuovo
tipo di Evento è questione di poche righe di
codice. L'unico particolare degno di nota è
che il costruttore del nuovo tipo di evento deve invocare
il costruttore della superclasse, passando un reference
alla sorgente dell'Evento.
L'interfaccia
che rappresenta l'ascoltatore deve estendere l'interfaccia
EventListener; a parte questo al suo interno si può
definire un numero arbitrario di metodi, la cui unica
costante è quella di avere come parametro un
reference all'Evento da propagare.
package
com.mokabyte.mokabook.javaBeans.timer;
import
com.mokabyte.mokabook.javaBeans.timer.*;
public
interface TimerListener extends java.util.EventListener
{
public void clockTicked(TimerEvent e);
}
Per
finire, ecco il Bean vero e proprio. Come si può
notare, esso implementa l'interfaccia Serializable
che rende possibile la serializzazione.
package
com.mokabyte.mokabook.javaBeans.timer;
import
com.mokabyte.mokabook.javaBeans.*;
import java.io.*;
import java.util.*;
public
class TimerBean implements Serializable {
private
int time = 1000;
private transient TimerThread timerThread;
private Vector timerListeners = new Vector();
public
void addTimerListener(TimerListener t) {
timerListeners.add(t);
}
public void removeTimerListener(TimerListener
t) {
timerListeners.remove(t);
}
protected void fireTimerEvent(TimerEvent
e) {
Enumeration listeners = timerListeners.elements();
while(listeners.hasMoreElements())
((TimerListener)listeners.nextElement()).clockTicked(e);
}
public synchronized void setMillis(int
millis) {
time = millis;
}
public synchronized int getMillis() {
return time;
}
public synchronized void startTimer()
{
if(timerThread!=null)
forceTick();
timerThread = new TimerThread();
timerThread.start();
}
public synchronized void stopTimer() {
if(timerThread==null)
return;
timerThread.killTimer();
timerThread = null;
}
public
synchronized void forceTick() {
if(timerThread!=null) {
stopTimer();
startTimer();
}
else
fireTimerEvent(new
TimerEvent(this));
}
class
TimerThread extends Thread {
private boolean running =
true;
public synchronized void killTimer() {
running = false;
}
private
synchronized boolean isRunning() {
return running;
}
public
void run() {
while(true)
try {
if(isRunning())
{
fireTimerEvent(new
TimerEvent(TimerBean.this));
Thread.sleep(getMillis());
}
else
break;
}
catch(InterruptedException
e) {}
}
}
}
I
primi tre metodi servono a gestire la lista degli
ascoltatori. Il terzo e il quarto gestiscono la proprietà
'millis', ossia la il tempo, in millisecondi, tra
un tick e l'altro. I due metodi successivi, startTimer,
stopTimer, servono ad avviare e fermare il timer,
mentre forceTick lancia un tick e riavvia il timer,
se questo è attivo. Il timer vero e proprio
viene implementato grazie ad una classe interna TimerThread,
sottoclasse di Thread. Si noti il metodo killTimer,
che permette di terminare in modo pulito la vita del
Thread: questa soluzione è da preferire al
metodo stop (deprecato a partire dal JDK 1.1), che
in certi casi può provocare la terminazione
del Thread in uno stato inconsistente.
Per
compilare le classi del Bean, bisogna usare la seguente
riga di comando:
javac
com\mokabyte\mokabook\javaBeans\timer\*.java
Per
impacchettare il Bean in un file Jar, dobbiamo per
prima cosa creare con un editor di testo il file timerManifest.tmp,
con le seguenti righe:
Name:
com/mokabyte/mokabook/javaBeans/timer/TimerBean.class
Java-Bean: True
Per
creare l'archivio dobbiamo quindi digitare il seguente
comando:
jar
cfm timer.jar timerManifest.tmp com\mokabyte\mokabook\javaBeans\timer\*.class
Per
testare classe TimerBean, possiamo usare il seguente
programma, che crea un oggetto TimerBean e registra
un TimerListener che stampa a video una scritta ad
ogni tick del timer.
package
com.mokabyte.mokabook.javaBeans.timer;
import
com.mokabyte.mokabook.javaBeans.timer.*;
public
class TimerTest {
public static void main(String argv[])
{
TimerBean t = new TimerBean();
t.addTimerListener(new TimerListener()
{
public void clockTicked(TimerEvent
e) {
System.out.println("Tick");
}
});
t.startTimer();
}
}
Conclusioni
Questo
mese abbiamo studiato il meccanismo di propagazione
degli eventi Bean, senza dubbio lo strumento più
importante per la comunicazione tra componenti. Il
mese prossimo studieremo l'introspezione, ed impareremo
a curare l'integrazione dei Beans all'interno di strumenti
grafici.
Gli
esempi allegati si possono trovare qui
|