Introduzione
Quando si parla di JavaBean si dovrebbe intendere un
oggetto che è conforme alle omonime specifiche
redatte da Sun in collaborazione con altre società,
con l'intento iniziale di impostare delle basi comuni
per la costruzione di componenti completi e riutilizzabili.
Una delle più interessanti caratteristiche di
questi oggetti, consiste nel fornire anche alcuni patterns
per la loro definizione, in modo tale che possano essere
inclusi all'interno dei più comuni IDE e quindi
utilizzati anche in modo visuale. Lo sviluppatore, in
modo semplice e relativamente veloce, è in grado
di assemblare i vari oggetti, anche senza conoscerli
approfonditamente, in modo da costruire applicazioni
più complesse.
Figura 1 - La pagina delle specifiche ufficiali
SUN, dei JavaBeans,
all'indirizzo http://java.sun.com/products/javabeans/
Un
JavaBean
Le specifiche Sun stabiliscono in modo chiaro quali
siano quegli oggetti o classi Java che possono definirsi
JavaBean o meno, ma di queste caratteristiche, quelle
che sono senza dubbio fondamentali per uno sviluppatore
sono:
- La
classe deve essere public (pubblica) e deve avere
un costruttore public che non accetta argomenti. Questo
consente ai tools ed alle applicazioni di costruire
una istanza di questo oggetto in maniera dinamica,
senza conoscerne il nome o le caratteristiche:
String
nomeClasse = ......;
Object miaClasse = Class.forName(nomeClasse).newInstance();
-
Proprio per la presenza di un costruttore senza argomenti,
la configurazione della classe deve avvenire in modo
completamente separato dalla sua inizializzazione,
attraverso l'utilizzo di proprietà (spesso
chiamati attributi) che contengono le informazioni
sui dati e sul comportamento della classe stessa.
- Le
specifiche chiariscono che questi attributi devono
avere dei nomi che iniziano con la lettera minuscola
e devono contenere solo caratteri che sono accettati
come identificatori Java. Per accedere (sia leggere
che definire) a queste proprietà si devono
utilizzare dei metodi (i famosi "getters"
e "setters") che devono seguire dei pattern
precisi per il loro nome, che prevedono l'uso di get
o set, rispettivamente per leggere e definire gli
attributi stessi, seguito dal nome della proprietà
con l'iniziale maiuscola. L'unica eccezione è
nel caso di un attributo di tipo boolean (primitiva
e non oggetto Boolean). In questo caso il nome del
getter utilizzerà is al posto di get.
- Nel
caso di un setter, il metodo deve contenere un parametro
dello stesso tipo dell'attributo da definire. Nel
caso di un getter, il metodo ritornerà un oggetto
o una primitiva, ma sempre dello stesso tipo della
proprietà che si intende leggere.
- Non
è obbligatorio fornire i "getter"
e i "setter" per ogni proprietà dell'oggetto.
Quando si reputa che un attributo debba o possa essere
solo in lettura (read-only), o in sola scrittura (write-only),
basta non creare l'apposito metodo.
Ecco
un semplicissimo esempio di un bean costruito seguendo
le caratteristiche evidenziate sino ad ora, attraverso
la rappresentazione del classico studente all'interno
di una scuola:
public
class Studente {
private
String nome;
private String cognome;
private int anno;
private boolean nuovo;
public Studente() {
// costruttore vuoto
}
// ... altri costruttori
public void setNome(String arg) {
nome = arg;
}
public
String getNome() {
return nome;
}
public void setCognome(String arg) {
cognome = arg;
}
public
String getCognome() {
return cognome;
}
public void setAnno(int arg) {
anno = arg;
}
public
int getAnno() {
return anno;
}
public void setNuovo(boolean arg) {
nuovo = arg;
}
public boolean isNuovo() {
return nuovo;
}
// altri metodi...
}
Le
specifiche Sun vanno comunque oltre. Definiscono, ad
esempio, una serie di pattern per la gestione degli
eventi e moltre altre caratteristiche che però
non sono legate all'argomento principale di questo articolo,
ossia il componente BeanUtils del progetto Jakarta Commons.
Figura 2 - La homepage del componente BeanUtils
del progetto Jakarta Commons, all'indirizzo http://jakarta.apache.org/commons/beanutils/index.html
Come
spesso accade, anche BeanUtils è nato unendo
alcune librerie e piccoli pacchetti che erano stati
sviluppati all'interno di altri progetti. L'idea non
è solo quella di fornire una serie di strumenti
e di metodi di supporto che siano utili agli sviluppatori
per semplificare la gestione e l'utilizzo dei JavaBeans.
Questo componente, infatti, permette di utilizzare i
JavaBeans in maniera dinamica, ossia senza conoscerne
le proprietà in fase di compilazione, o anche
prevedere la possibilità di costruire dei bean
senza dover realmente costruire la classe.
I possibili scenari di utilizzo sono vari, uno tra i
più interessanti potrebbe essere quello di una
applicazione che contiene un alto numero di query SQL.
Grazie alla possibilità offerta dai bean dinamici,
è possibile costruire un bean come risultato
di una query, senza dover attualmente scrivere la classe
del bean vera e propria, con grosso risparmio di tempo,
non solo in fase di sviluppo, ma anche in fase di progettazione.
Ma andiamo per gradi...
Standard
JavaBeans
Se, all'interno di un nostro programma avessimo la necessità
di interagire con il bean che abbiamo creato in precedenza,
la rappresentazione di uno studente, potremmo utilizzare
semplicemente i vari getters e setters che lo compongono.
Ma ci possono essere situazioni più complesse,
nelle quali diventa necessario instanziare una classe
ed utilizzarne i vari metodi, senza conoscerla in precedenza.
Il linguaggio Java ci permette di raggiungere questo
obiettivo attraverso l'uso di classi standard facenti
parte della Reflection o della Introspection. Ma nonostante
non sia una cosa impossibile, questa operazione non
è certo semplice, in particolare per chi è
alle prime armi.
Ecco uno dei punti dove BeanUtils che si dimostra essere
un valido aiuto per il programmatore. Le API di questo
componente infatti, permettono di accedere ai bean ed
alle loro proprietà in modo dinamico, senza doverli
definire in fase di compilazione.
Nel nostro caso, ad esempio, potremmo accedere e definire
le proprietà del nostro bean Studente in questo
modo:
...
Studente stu = ...;
// definizione proprieta'
PropertyUtils.setSimpleProperty(stu, "nome",
"Alessandro");
PropertyUtils.setSimpleProperty(stu, "cognome",
"Garbagnati");
PropertyUtils.setSimpleProperty(stu, "anno",
new Integer(1966));
// lettura proprieta'
String nome = (String)PropertyUtils.getSimpleProperty(stu,
"nome"));
String cognome = (String)PropertyUtils.getSimpleProperty(stu,
"cognome"));
Integer anno = (Integer)PropertyUtils.getSimpleProperty(stu,
"anno"));
In
questo caso sono stati usati i metodi che servono per
definire o leggere i valori di proprietà semplici,
ossia proprietà che contengono e ritornano un
singolo valore, sia esso una primitiva, o un oggetto
più o meno complesso. Nel caso di primitive,
come si vede dall'esempio, il metodo necessita del suo
"wrapper" (java.lang.Integer per gli int,
java.lang.Boolean per i boolean, java.lang.Characher
per i char, ecc. ecc.).
Ma le proprietà possono anche essere più
complesse, ossia contenere più valori sia attraverso
l'uso di arrays o collezioni oppure attraverso l'uso
di ogni tipo di mappe.
Per
esempio, al nostro studente vogliamo aggiungere anche
una mappa che contenga la media dei voti per ogni materia:
...
private Map materie;
...
public void setMateria(String nome, double media) {
if (materie == null) {
materie = new HashMap();
}
materie.put(nome,
new Double(media));
}
public double getMateria(String nome) {
if
(materie == null) {
materie
= new HashMap();
}
materie.put(nome,
new Double(media));
}
...
In
questo caso, per aggiungere e leggere queste proprietà
utilizzando le API di JavaBeans, dovremmo usare:
...
Studente stu = ...;
// aggiunta materie
PropertyUtils.setMappedProperty(stu, "materia",
"inglese", new Double(7));
PropertyUtils.setMappedProperty(stu, "materia",
"storia", new Double(7));
// lettura materie
Double inglese = (Double)PropertyUtils.getMappedProperty(stu,
"materia", "inglese");
Double storia = (Double)PropertyUtils.setMappedProperty(stu,
"materia", "storia");
oppure,
utilizzando una sintassi differente
...
Studente stu = ...;
// aggiunta materie
PropertyUtils.setMappedProperty(stu, "materia(inglese)",
new Double(7));
PropertyUtils.setMappedProperty(stu, "materia(storia)",
new Double(7));
// lettura materie
Double inglese = (Double)PropertyUtils.getMappedProperty(stu,
"materia(inglese)");
Double storia = (Double)PropertyUtils.setMappedProperty(stu,
"materia(storia)");
Nel
caso di una proprietà indicizzata, tipo array
o implementazioni dell'interfaccia java.util.List, dovremmo
invece utilizzare una sintassi di questo genere:
...
Studente stu = ...;
// aggiunta valore
PropertyUtils.setIndexedProperty(stu, "valore",
1, "uno");
PropertyUtils.setIndexedProperty(stu, "valore",
2, "due");
// lettura valori
String val1 = (String)PropertyUtils.getIndexedProperty(stu,
"valore", 1);
String val2 = (String)PropertyUtils.getIndexedProperty(stu,
"valore", 2);
e
anche in questo caso esiste una seconda sintassi:
...
Studente stu = ...;
// aggiunta valore
PropertyUtils.setIndexedProperty(stu, "valore[1]",
"uno");
PropertyUtils.setIndexedProperty(stu, "valore[2]",
"due");
// lettura valori
String val1 = (String)PropertyUtils.getIndexedProperty(stu,
"valore[1]");
String val2 = (String)PropertyUtils.getIndexedProperty(stu,
"valore[2]");
Vi
sono poi situazioni nelle quali una proprietà
di un bean corrisponde ad un altro bean. Ad esempio
si potrebbe avere un oggetto che rappresenta una Classe
scolastica:
public
class Classe {
private
String nome;
private List studenti;
public
Classe() {
//
costruttore null
}
// altri costruttori...
public void setNome(String arg) {
nome
= arg;
}
public String getNome() {
return
nome;
}
public void setStudente(int indice, Studente studente)
{
if
(studenti == null) {
studenti
= new ArrayList();
}
//
per semplificare il tutto, si aggiunge l'elemento
//
direttamente in coda
studenti.add(studente);
}
public Studente getStudente(int indice) {
if
(studenti != null) {
return
(Studente)studenti.get(indice);
}
return
null;
}
}
A
questa classe possiamo dare un nome ed aggiungere una
serie di studenti:
...
Studente stu1 = ...;
Studente stu2 = ...;
...
Classe classe = ...;
...
PropertyUtils.setIndexedProperty(classe, "studente[0]",
stu1);
PropertyUtils.setIndexedProperty(classe, "studente[1]",
stu2);
...
Normalmente,
per accedere alle informazioni degli studenti dovremmo
farci dare un l'oggetto studente che ci interessa e
quindi accedere alle sue informazioni tramite i vari
getters. Ma con BeanUtils possiamo usare una interessante
funzionalità:
String
nome1 = (String)PropertyUtils.getNestedProperty(classe,
"studente[0].nome");
String nome2 = (String)PropertyUtils.getNestedProperty(classe,
"studente[1].nome");
e
se avessimo inserito anche le medie per le materie,
avremmo potuto usare questa sintassi:
Double
ing1 = (Double)PropertyUtils.getNestedProperty(classe,
"studente[0].materia(inglese)");
Double
ing2 = (Double)PropertyUtils.getNestedProperty(classe,
"studente[1].materia(inglese)");
JavaBeans dinamici
L'oggetto PropertyUtils, che abbiamo visto sino ad ora,
permette di accedere in modo dinamico ad un bean esistente,
senza dover in alcun modo intervenire manualmente sul
codice.
Vi sono delle situazioni, però, dove si vuole
accedere in modo dinamico alle proprietà di un
oggetto, senza però doverne fisicamente scrivere
la classe. Per fare questo, il componente BeanUtils
contiene due interfacce, org.apache.commons.beanutils.DynaBean
e org.apache.commons.beanutils.DynaClass.
Il nostro studente dell'esempio precedente, potrebbe
anche essere rappresentato con un bean dinamico ed in
tal caso si potrebbe accedere alle varie proprietà
in questo modo:
DynaBean
studente = ...;
// i dettagli per l'inizializzazione variano a seconda
// dell'implementazione del DynaBean scelta.
String nome = (String)studente.get("nome");
String cognome = (String)studente.get("cognome");
Double inglese = (Double)studente.get("materia",
"inglese");
E'
importante notare che la classe PropertyUtils, largamente
analizzata in precedenza, è stata realizzata
tenendo in considerazione la possibilità di usare
DynaBeans e quindi è in grado di utilizzare correttamente
sia i classici bean, sia le implementazioni di quelli
dinamici.
Come
accennato in precedenza, i bean dinamici si basano su
due interfacce che, ovviamente, devono essere implementate
per poter essere utilizzate. Gli strumenti che fanno
un pesante uso di questo componente normalmente implementano
queste interfacce in base alle proprie necessità
come, ad esempio, nel caso dei DynaActionForm o DynaValidatorActionForm
del progetto Jakarta Struts.
All'interno del pacchetto, sono comunque presenti alcune
implementazioni di base, grazie alle quali diventa possibile
utilizzare bean dinamici senza troppo sforzo.
E'
il caso dei BasicDynaBean e BasicDynaClass, che forniscono
un set minimale di funzionalità tramite le quali
è possibile definire le proprietà del
bean, attraverso istanze di DynaProperty. Ecco come
trasformare l'oggetto studente:
DynaProperty[]
props = new DynaProperty[] {
new DynaProperty("nome", java.lang.String.class),
new DynaProperty("cognome", java.lang.String.class),
new DynaProperty("anno", java.lang.Integer.class),
new DynaProperty("materie", java.util.Map.class),
};
BasicDynaClass dynaClass = new BasicDynaClass("studente",
null, props);
A
questo punto, per costruire nuovi studenti, basta creare
una istanza della classe appena creata e definire le
varie proprietà:
DynaBean
studente = dynaClass.newInstance();
studente.set("nome", "Alessandro");
studente.set("cognome", "Garbagnati");
studente.set("anno", new Integer(1966));
studente.set("materie", new HashMap());
studente.set("materie", "inglese",
new Double(7));
Accedervi
sarà altrettanto semplice:
String
nome = (String)studente.get("nome");
Double inglese = (Double)studente.get("materie",
"inglese");
E'
possibile che, iniziando ad utilizzare i bean dinamici,
si senta la necessità di cercare di mantenere
uno standard all'interno del codice e quindi si possa
desiderare accedere a tutti i beans allo stesso modo.
Ecco perché all'interno della libreria sono presenti
anche due implementazioni: org.apache.commons.beanutils.WrapDynaBean
e org.apache.commons.beanutils.WrapDynaClass, che, come
è facile intuire dal nome, permettono di "wrappare"
(ossia avvolgere) un bean normale, all'intero di un
bean dinamico, in modo che a tutti i beans (dinamici
e non) è possibile accedere nello stesso modo.
Il
nostro bean studente, che abbiamo scritto con tanta
fatica, può comunque essere utilizzato come un
bean dinamico semplicemente in questo modo:
//
definizione classe
DynaBean studente = new WrapDynaBean(new Studente());
studente.set("nome", "Alessandro");
studente.set("cognome", "Garbagnati");
studente.set("anno", new Integer(1966));
studente.set("materia", "inglese",
new Double(7));
// accesso alle proprieta'
String nome = (String)studente.get("nome");
Double inglese = (Double)studente.get("materie",
"inglese");
Qualche
aiuto per i database
Tra le implementazioni di base che sono presenti di
default, all'interno del componente, ce ne sono alcuni
che sono estremamente interessanti per chi sviluppa
applicazioni che si appoggiano direttamente su un database,
attraverso l'uso di jdbc. Sto parlando di org.apache.commons.beanutils.ResultSetDynaClass
e org.apache.commons.beanutils.RowSetDynaClass.
Supponiamo
di avere tutte le informazioni scolastiche all'interno
di un database e voler eseguire una serie di query,
per ottenere diverse visioni delle informazioni, magari
fornendo anche una applicazione che utilizza un sistema
(query builder) che genera le query in tempo reale in
base alle richieste dei vari utenti. E' ovvio che non
esiste modo, per il programmatore, di prevedere quali
campi verranno richiesti e, quindi, estratti. Progettare
dei bean che contengano i dati di queste query diventa
estremamente complesso.
Ma con BeanUtils, anche questo diventa quasi un gioco
da ragazzi:
//
acquisisci la connessione
Connection conn = ...;
...
Statement stat = conn.createStatement();
ResultSet rset = stat.executeQuery("SELECT s1.nome,
m2.materia, .....");
Iterator rows = (new ResultSetDynaClass(rset)).iterator();
while (rows.hasNext()) {
DynaBean row = (DynaBean)rows.next();
System.out.println("Studente "
+ row.get("nome"));
System.out.println("Materia "
+ row.get("materia"));
...
}
rset.close();
stat.close();
conn.close();
Già,
tutto qui! Non è in alcun modo necessario creare
un bean, perché questa implementazione automaticamente
ne creerà uno utilizzando le caratteristiche
dei campi che compongono la query.
L'unico problema che si presenta è che questa
implementazione richiede che il ResultSet non venga
chiuso e, quindi, la connessione al database deve rimanere
aperta. Per sopperire a questo, esiste una seconda implementazione,
org.apache.commons.beanutils.RowSetDynaClass che rappresenta
con un bean dinamico, un ResultSet scollegato.
//
acquisisci la connessione
Connection conn = ...;
...
Statement stat = conn.createStatement();
ResultSet rset = stat.executeQuery("SELECT s1.nome,
m2.materia, .....");
RowSetDynaClass dRows = new RowSetDynaClass(rset);
rset.close();
stat.close();
conn.close();
...
List rows = dRows.getRows();
...
In
questo caso, la connessione viene chiusa immediatamente
dopo e i dati possono essere analizzati ed utilizzati
con calma.Non male, vero?
Il
componente Commons BeanUtils non si limita, ovviamente,
a fornire classi per la gestione dinamica dei bean o
per l'utilizzo di bean dinamici. All'interno della libreria,
infatti, si può trovare tutta una serie di classi
che forniscono agli sviluppatori metodi con i quali
vengono semplificate molte operazioni legate all'uso
dei beans.
Figura 3 - La pagina iniziale del JavaDoc di Commons
BeanUtils presente sia all'interno del pacchetto, oppure
accessibile online all'indirizzo http://jakarta.apache.org/commons/beanutils/api/index.html
Alcuni
piccoli esempi sono forniti dalla classe org.apache.commons.beanutils.BeanUtils,
che contiene metodi per semplificare le operazioni di
copia delle proprietà da un oggetto ad un altro,
o la creazione di una copia di un bean, anche se lo
stesso non implementa l'interfaccia java.lang.Cloneable.
Altri esempi si possono trovare nella classe org.apache.commons.beanutils.ConvertUtils
e il package org.apache.commons.beanutils.converters,
per la gestione delle conversioni all'interno delle
proprietà.
Conclusioni
Sono numerosi i progetti, sia del gruppo Jakarta che
non, che utilizzano questo componente. Tra i più
famosi e diffusi possiamo citare Tomcat, l'implementazione
di riferimento per Sun delle specifiche Servlet e JSP
realizzata dal gruppo Jakarta. Sempre dello stesso gruppo
c'è Struts, il framework per web application
del gruppo Jakarta, oppure lo stesso componente Commons
Digester, già visto in questa serie di articoli.
Al di fuori del gruppo Jakarta, ma parte dei progetti
di SourceForge si può citare, tra gli altri,
Hibernate, il framework per la gestione persistente
dei dati. La lista, comunque, sarebbe molto lunga...
Questa
è, senza dubbio, la miglior conferma come BeanUtils
abbia oramai raggiunto un livello di maturazione tale
da poter essere utilizzato tranquillamente all'interno
di progetti diffusi e in produzione. Ad oggi la versione
attuale è la 1.6.1, mentre si sta lavorando alla
nuova versione 1.7. Per scaricarla, basta utilizzare
la pagina http://jakarta.apache.org/site/binindex.cgi
per la versione binaria mentre all'indirizzo http://jakarta.apache.org/site/sourceindex.cgi
è possibile scaricare la versione sorgente.
Unica nota... per poter funzionare, BeanUtils necessita
di due librerie, sempre appartenenti al progetto Commons,
ossia Collections e Loggings (le versioni binarie sono
all'indirizzo http://jakarta.apache.org/site/binindex.cgi).
Link
e risorse
Scarica gli esempi
allegati all'articolo
|