MokaByte 82 - Febbraio 2004 
Jakarta Commons
V parte: Jakarta BeanUtils
di
Alessandro "Kazuma" Garbagnati
Spesso e volentieri, all'interno delle nostre applicazioni, facciamo un largo uso dei JavaBeans, qualche volta senza nemmeno rendercene conto. I JavaBeans sono infatti da considerarsi come uno strumento fondamentale per lo sviluppo di ogni tipologia di applicazione. Qualche volta però, utilizzare questi oggetti risulta lungo o tedioso, altre volte anche complesso. Ecco perché il componente BeanUtils del progetto Jakarta Commons, può diventare estremamente utile.

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

 
MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it