Guida
all'Implementazione dei JavaBeans
Realizzare
un componente Java Bean è un compito alla portata
di qualunque programmatore Java che disponga di buone
conoscenze di sviluppo Object Oriented. Nei paragrafi
seguenti verranno descritte dettagliatamente le convenzioni
di naming dettate dalla specifica, e verranno fornite
le istruzioni su come scrivere le poche righe di codice
necessarie ad implementare i meccanismi che caratterizzano
i servizi Bean. Infine verranno presentati degli esempi,
che permetteranno di impratichirsi con il processo
di implementazione delle specifiche.
Le
Proprietà
Le
Proprietà sono attributi che descrivono l'aspetto
e il comportamento di un Bean, e che possono essere
modificate durante tutto il ciclo di vita del Componente.
Di base, le Proprietà sono attributi privati,
ai quali si accede attraverso una coppia di metodi
della forma:
public
<PropertyType> get<PropertyName>()
public
void set<PropertyName>(<PropertyType> property)
La
convenzione di aggiungere il prefisso get e set ai
metodi che forniscono l'accesso ad una proprietà,
permette ai Tool grafici di rilevare le proprietà
Bean, determinarne le regole di accesso (Read Only
o Read/Write), dedurne il tipo, visualizzare le proprietà
su un apposito property Sheet e individuare l'Editor
di Proprietà più adatto al caso.
Se
ad esempio un Tool grafico scopre, grazie all'introspezione,
la coppia di metodi:
public
Color getForegroundColor() { ... }
public void setForegroundColor(Color c) { ... }
da
questi conclude che esiste una proprietà chiamata
"foregroundColor" (notare la prima lettera minuscola),
accessibile sia in lettura che in scrittura, di tipo
Color. A questo punto, il Tool può cercare
un Editor di Proprietà per parametri di tipo
Color (ad esempio una palette tipo ColorChooser),
e mostrare la proprietà su un property sheet
in modo che possa essere vista e manipolata dal programmatore.
Proprietà
Indicizzate (Indexed Property)
Le
proprietà indicizzate permettono di gestire
collezioni di valori accessibili attraverso indice,
in maniera simile a come si fa con un vettore. Lo
schema di composizione dei metodi di accesso di una
proprietà indicizzata è il seguente:
public <PropertyType>[] get<PropertyName>();
public void set<PropertyName>(<PropertyType>[]
value);
per
i metodi che permettono di manipolare l'intera collection,
mentre per accedere ai singoli elementi, si deve predisporre
una coppia di metodi del tipo:
public
<PropertyType> get<PropertyName>(int index);
public void set<PropertyName>(int index, <PropertyType>
value);
Proprietà
Bound
Le
proprietà semplici, così come sono state
descritte nei paragrafi precedenti, seguono una convenzione
radicata da tempo nella normale programmazione ad
oggetti. Le proprietà bound, al contrario,
sono caratteristiche dell'universo dei componenti,
dove si verifica molto spesso la necessità
di collegare il valore delle proprietà di un
Componente a quelli di un'altro, in modo tale che
si mantengano aggiornati. I metodi 'set' delle proprietà
bound, inviano una notifica a tutti gli ascoltatori
registrati ogni qualvolta viene alterato il valore
della proprietà. Il meccanismo di ascolto-noltifica,
simile a quello degli Eventi Swing ed AWT, segue il
pattern Observer.
Figura
1 - Il meccansimo di notifica di eventi bound
segue il pattern Observer
Le
proprietà bound, a differenza degli Eventi
Swing, utilizzano un unico tipo di evento, ChangeEvent,
cosa che semplifica il processo di implementazione.
La classe PropertyChangeSupport, presente all'interno
del package java.bean, fornisce i metodi che gestiscono
la lista degli ascoltatori, e quelli che producono
l'invio degli eventi.
Un
oggetto che voglia mettersi in ascolto di una proprietà,
deve implementare l'interfaccia PropertyChangeListener,
e deve registrarsi presso la sorgente di eventi .
L'oggetto PropertyChangeEvent incapsula le informazioni
riguardo alla proprietà modificata, la sorgente
ed il valore della proprietà.
Come
Implementare il supporto alle proprietà Bound
Per
aggiungere ad un Bean il supporto alle proprietà
bound, bisogna anzitutto importare il package java.beans.*,
in modo da garantire l'accesso alle classi PropertyChangeSupport
e PropertyChangeEvent. Quindi bisogna creare un oggetto
PropertyChangeSupport, che ha il compito di mantenere
la lista degli ascoltatori, e di fornire i metodi
che gestiscono l'invio degli eventi:
private
PropertyChangeSupport changes =
new PropertyChangeSupport(this);
A
questo punto bisogna realizzare, nella propria classe,
i metodi che permettono di gestire la lista degli
ascoltatori. Tali metodi sono dei semplici metodi
wrapper che fanno riferimento a metodi con la stessa
firma, presenti nel PropertyChangeSupport:
public void addPropertyChangeListener(
PropertyChangeListener l) {
changes.addPropertyChangeListener(l);
}
public void removePropertyChangeListener(
PropertyChangeListener l) {
changes.removePropertyChangeListener(l);
}
La
presenza di dei metodi addPropertyChangeListener e
removePropertyChangeListener permette ai Tool grafici
di riconoscere un oggetto in grado di inviare proprietà
Bound, e di mettere a disposizione un'opportuna voce
nel menù di gestione degli eventi.
L'ultimo
passaggio consiste nel modificare i metodi set relativi
alle proprietà che si vuole rendere bound,
per fare in modo che venga generato un PropertyChangeEvent
ogni volta che la proprietà viene reimpostata:
public void setColor(Color newColor) {
Color oldColor = color;
color = newColor;
changes.firePropertyChange("color",
oldColor , newColor);
}
Nel
caso di proprietà read only, prive di metodo
set, l'invio dell'evento dovrà avvenire all'interno
del metodo che attua la modifica della proprietà.
Un'aspetto interessante del meccanismo di invio di
PropertyChangeEvent, è che essi trasportano
sia il nuovo valore che quello vecchio. Questa scelta
dispensa chi implementa un ascoltatore dal compito
di mantenere una copia del valore, qualora questo
fosse necessario, dal momento che l'evento viene propagato
dopo la modifica della relativa proprietà.
Il metodo fireChangeEvent() della classe PropertyChangeListener
fornisce il servizio di Event dispatching:
firePropertyChange(String
propertyName, Object oldValue,
Object newValue)
In
pratica esso impacchetta i parametri in un oggetto
PropertyChangeEvent, e chiama il metodo propertyChange(PropertyChangeEvent
p) su tutti gli ascoltatori registrati. I parametri
vengono trattati come Object, e nel caso si debbano
inviare proprietà espresse in termini di tipi
primitivi, occorre incapsularle nell'opportuno wrapper
(Integer per valori int, Double per valori double
e così via). Per facilitare questo compito,
la classe propertyChangeSupport prevede delle varianti
di firePropertyChange per valori int e boolean.
Come
Implementare il supporto alle proprietà Bound
su sottoclassi di JComponent
La
classe JComponent, superclasse di tutti i componenti
Swing, dispone del supporto nativo alla gestione di
proprietà Bound. Di base, essa fornisce li
metodi addPropertyChangeListener e removePropertyChangeListener,
oltre ad una collezione di metodi firePropertyChange
adatta ad ogni tipo primitivo. In questo caso l'implementazione
di una proprietà bound richiederà solo
una modifica al metodo set preposto, in modo simile
a come descritto nell'ultimo passaggio del precedente
paragrafo, con la differenza che non è necessario
ricorrere ad un oggetto propertyChangeSupport per
inviare la proprietà:
public
void setColor(Color newColor) {
Color oldColor = color;
color = newColor;
firePropertyChange("color",
oldColor , newColor);
}
Ascoltatori
di Proprietà
Se
si desidera mettersi in ascolto di una proprietà,
bisogna definire un'opportuno oggetto PropertyChangeListener,
e registrarlo presso il Bean. Un PropertyChangeListener
deve definire il metodo propertyChange(PropertyChangeEvent
e), che viene chiamato quando avviene la modifica
di una proprietà bound.
Un
PropertyChangeListener viene notificato quando avviene
la modifica di una qualunque proprietà bound:
per questa ragione esso deve, come prima cosa, verificare,
che la proprietà appena modificata sia quella
alla quale si è interessati. Una simile verifica
richiede una chiamata al metodo getPropertyName di
PropertyChangeEvent, che restituisce il nome
della proprietà. Per convenzione, i nomi di
proprietà vengono estratti dai nomi dichiarati
nei metodi get e set, con la prima lettera minuscola.
Il seguente frammento di codice presenta un tipico
PropertyChangeListener, che ascolta la proprietà
'foregroundColor':
public class Listener implements PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent e)
{
if(e.getPropertyName().equals("foregroundColor"))
System.out.println(e.getNewValue());
}
}
Un
esempio di Bean con proprietà Bound
Un
JavaBean rappresenta un mattone di un programma. Ogni
componente è un'unità di utilizzo abbastanza
grossa da incorporare una funzionalità evoluta,
ma piccola rispetto ad un programma fatto e finito.
Il concetto del riuso può essere presente a
diversi livelli del progetto: il seguente Bean fornisce
un esempio di elevata versatilità
Il
Bean PhotoAlbum è un pannello grafico al cui
interno vengono caricate delle immagini. Il metodo
showNext() permette di passare da un'immagine all'altra,
in modo ciclico. Il numero ed il tipo di immagini
viene determinato al momento dell'avvio: durante la
fase di costruzione viene letto il file comment.txt,
presente nella directory images, che contiene una
riga di commento per ogni immagine presente nella
cartella. Le immagini devono essere nominate in modo
progressivo (img0.jpg, img1.jpg, img2.jpg....) e devono
essere presenti in numero uguale alle righe del file
comment.txt. Questa scelta progettuale consente di
introdurre il riuso ad un livello abbastanza alto:
qualunque utente, anche con scarse conoscenze del
linguaggio, può personalizzare il componente,
inserendo le sue foto preferite, senza la necessità
di alterare il codice sorgente.
Il
Bean PhotoAlbum ha tre proprietà:
- imageNumber,
che restituisce il numero di immagini contenute
nell'album. Essendo una quantità immutabile,
tale proprietà è stata implementata
come proprietà semplice.
- imageIndex:
restituisce l'indice dell'immagine attualmente visualizzata.
Al cambio di immagine viene inviato un PropertyChangeEvent
- imageComment:
restituisce una stringa di commento all'immagine.
Anche in questo caso, al cambio di immagine viene
generato un PropertyChangeEvent
Il
Bean viene definito come sottoclasse di JPanel: per
questo motivo non vengono dichiarati i metodi addPropertyChangeListener
e removePropertyChangeListener, già presenti
nella superclasse. L'invio delle proprietà verrà
messo in atto grazie al metodo firePropertyChange di
JComponent.
package
com.mokabyte.mokabook.javaBeans.photoAlbum;
import
java.awt.*;
import
java.beans.*;
import
java.io.*;
import
java.net.*;
import
java.util.*;
import
javax.swing.*;
public
class PhotoAlbum extends JPanel {
private Vector comments = new Vector();
private int imageIndex;
public PhotoAlbum() {
super();
setLayout(new BorderLayout());
setupComments();
imageIndex=0;
showNext();
}
private void setupComments() {
try {
URL indexUrl =
getClass().getResource("images/"+"comments.txt");
InputStream in = indexUrl.openStream();
BufferedReader lineReader =
new BufferedReader(new InputStreamReader(in));
String line;
while((line = lineReader.readLine())!=null)
comments.add(line);
}
catch(Exception e) {
e.printStackTrace();
}
}
public int getImageNumber() {
return comments.size();
}
public int getImageIndex() {
return imageIndex;
}
public String getImageComment() {
return (String)comments.elementAt(imageIndex);
}
public void showNext() {
int oldImageIndex = imageIndex;
imageIndex = ((imageIndex+1)%comments.size());
String imageName = "img"+Integer.toString(imageIndex)+".jpg";
showImage(getClass().getResource("images/"+imageName));
String oldImageComment =
(String)comments.elementAt(oldImageIndex);
String currentImageComment =
(String)comments.elementAt(imageIndex);
firePropertyChange("imageComment",oldImageComment,
currentImageComment);
firePropertyChange("imageIndex",oldImageIndex,imageIndex);
}
private void showImage(URL imageUrl) {
ImageIcon img = new ImageIcon(imageUrl);
JLabel picture = new JLabel(img);
JScrollPane pictureScrollPane = new JScrollPane(picture);
removeAll();
add(BorderLayout.CENTER,pictureScrollPane);
validate();
}
}
Possiamo
testare il Bean come fosse una normale classe Java,
utilizzando queste semplici righe di codice:
package
com.mokabyte.mokabook.javaBeans.photoAlbum;
import
com.mokabyte.mokabook.javaBeans.photoAlbum.*;
import
java.beans.*;
import
javax.swing.*;
public
class PhotoAlbumTest {
public
static void main(String argv[]) {
JFrame f = new JFrame("Photo Album");
PhotoAlbum p = new PhotoAlbum();
f.getContentPane().add(p);
p.addPropertyChangeListener(new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent e)
{
System.out.println(e.getPropertyName()+": "+e.getNewValue());
}
});
f.setSize(500,400);
f.setVisible(true);
while(true)
for(int i=0;i<7;i++) {
p.showNext();
try {Thread.sleep(1000);}catch(Exception e) {}
}
}
}
Figura
2 - Un programma di prova per il
Bean PhotoAlbum
Creazione
di un file jar
Prima
di procedere alla consegna del Bean entro un file
jar, bisogna anzitutto compilare le classi PhotoAlbum.java
e PhotoAlbumTest.java, che devono trovarsi nella cartella
com\mokabyte\mokabook\javaBeans\
javac
com\mokabyte\mokabook\javaBeans\photoAlbum\*.java
A
questo punto bisogna creare, ricorrendo ad un semplice
editor di testo tipo Notepad, un file photoAlbumManifest.tmp
con il seguente contenuto:
Main-Class:
com.mokabyte.mokabook.javaBeans.photoAlbum.PhotoAlbumTest
Name:
com/mokabyte/mokabook/javaBeans/photoAlbum/PhotoAlbum.class
Java-Bean:
True
Le
prime due righe, opzionali, segnalano la presenza
di una classe dotata di metodo main: in questo modo
il file jar potrà essere avviato digitando:
java
PhotoAlbum.jar
o
semplicemente con un doppio click sull'icona del file,
che diventa in questo modo un eseguibile interpiattaforma.
Figura
3 - Un file Jar opportunamente confezionato
si comporta come un
file
eseguibile multipiattaforma, attivabile con un doppio
click del mouse
Le
ultime due righe del file manifest specificano che
la classe PhotoAlbum.class è un JavaBean. Se
l'archivio contiene più di un bean, è
necessario elencarli tutti.
Per
generare l'archivio photoAlbum.jar, bisogna digitare
la riga di comando:
jar
cfm photoAlbum.jar photoAlbumManifest.tmp com\mokabyte\mokabook\javaBeans\photoAlbum\*.class
com\mokabyte\mokabook\javaBeans\photoAlbum\images\*.*
Il
file così generato contiene tutte le classi
e le immagini necessarie a dar vita al Bean PhotoAlbum.
Tale file potrà essere utilizzato facilmente
all'interno di Tool grafici o di pagine web, racchiuso
dentro un'applet.
NOTA:
le istruzioni fornite sono valide per la piattaforma
Windows. Su piattaforma Unix, le eventuali occorrenze
del simbolo "\", che funge da path separator su piattaforme
Windows, andranno sostituite col simbolo "/". Le convenzioni
adottate all'interno del file manifest valgono invece
su entrambe le piattaforme.
Integrazione
con altri Beans
Nonostante
il Bean PhotoAlbum fornisca un servizio abbastanza
evoluto, non è ancora classificabile come applicazione.
Esso, opportunamente integrato con altri Beans, può
comunque dar vita a numerosi programmi; vediamo qualche
esempio:
- Collegato
ad un CalendarBean, PhotoAlbum può dar vita
ad un simpatico calendario elettronico.
- Collegando
un bottone Bean al metodo showNext() possiamo creare
un album interattivo, impacchettarlo su un'applet
e pubblicarlo su Internet
- Impacchettando
il Bean PhotoAlbum con foto natalizie, e collegandolo
con un Bean Carillon, possiamo ottenere un biglietto
di auguri elettronico.
Figura
4 - Combinando, all'interno del Bean Box, il Bean
PhotoAlbum con un
pulsante
Bean, otteniamo una piccola applicazione
A
questi esempi se ne possono facilmente aggiungere
altri; altri ancora diventano possibili aggiungendo
al Bean nuovi metodi, come previousImage() e setImageAt(int
i); un compito ormai alla portata del lettore, che
fornisce un ottimo pretesto per esercitarsi.
Conclusioni
Questo
mese abbiamo parlato delle proprietà Bean,
un meccanismo di comunicazioni tra componenti molto
valido e potente. Grazie alle proprietà bound
è possibile fare in modo che il cambiamento
di una proprietà su un Bean provochi una reazione
su un altro, mantenendo intatta la consistenza. Nel
prossimo articolo parleremo di eventi Bean, un meccanismo
generico per la diffusione di eventi.
Allegati
Gli
esempi completi si possono scaricare qui
|