A cominciare da questo articolo introdurremo alcuni componenti del progetto Apache Commons. Di che cosa si tratta? Di una serie di librerie di utilizzo comune, pronte per essere incluse nei vostri progetti.
Spesso capita di implementare da zero funzionalità che sono già disponibile in librerie come queste. Conoscerle aiuta ad individuare una soluzione già pronta e ben realizzata; meglio fare ricorso a codice ben testato che scrivere in fretta e furia una soluzione all'incirca equivalente.
BeanUtils: che cosa offrono?
In questo articolo introdurremo il componente BeanUtils. Come il nome suggerisce si tratta di un insieme di classi per la manipolazione di Java Bean. La libreria ricorre all'uso della reflection e sfrutta la convenzione dei nomi per i metodi di accesso ed impostazione delle proprietà tipica di questo tipo di classi.
Prendiamo ad esempio la classe Automobile
:
public class Automobile {
...
public String getProduttore() { ... }
public void setProduttore(String produttore) { ... }
public String getModello() { ... }
public void setModello(String modello) { ... }
public long getAnnoDiImmatricolazione() { ... }
public void setAnnoDiImmatricolazione(long annoDiImmatricolazione) { ... }
public Alimentazione getAlimentazione() { ... }
public void setAlimentazione(Alimentazione alimentazione) { ... }
public boolean isCinquePorte() { ... }
public void setCinquePorte(boolean cinquePorte) { ... }
}
Analizzando i metodi esposti siamo in grado di capire quali siano le proprietà: i loro nomi ed i tipi. Queste informazioni possono essere ricavate dalla classe Automobile
o da un oggeto di quella classe tramite la libreria BeanUtils
. Scopriamo come, grazie ad una applicazione di esempio.
L'esempio: una "interfaccia flessibile"
La libreria BeanUtils può essere utilizzata in molti contesti. Per presentare un'utilizzo che sia da tutti comprensibile costruiamo un'applicazione che utilizzi un elemento noto ad ogni sviluppatore: la console. Il progetto che realizzeremo sarà in grado, dato un qualsiasi oggetto, di stamparne a video le proprietà o di ottenere da tastiera i valori necessari per impostarle. Sarà quindi una sorta di lettore/visualizzatore universale di bean.
Quello che svilupperemo è un esempio di "interfaccia flessibile", in grado cioè di adattarsi agli oggetti che gli vengono indicati. Utilizzando i principi illustrati in questo articolo si potrebbero realizzare interfacce grafiche o pagine Jsp in grado di gestire un qualsiasi oggetto.
Setup
Per realizzare la nostra applicazione possiamo utilizzare un qualsiasi ambiente di sviluppo. Cominciamo col creare un nuovo progetto (che chiamiamo BeansIO
). In questo progetto inseriamo fra i jar le librerie Apache Commons e Apache Logging. Entrambe possono essere scaricate dal sito ufficiale del progetto.
Il setup è completo e possiamo incominciare con il codice. Realizzeremo una classe, BeanConsoleIO
, che conterrà un metodo per leggere da console le proprietà di un qualsiasi bean ed impostarle ed un metodo per stamparne a video i dati. Oltre a questa ci sarà una classe di esempio in cui utilizzeremo le funzionalità di BeanConsoleIO
. Infine ci servirà definire una classe di un JavaBean; per questo scopo utilizzeremo la classe Automobile
esaminata in precedenza.
Leggere le proprietà del bean
Il metodo readBean della classe BeanConsoleIO
presenta questa struttura:
public void readBean(Object bean){
for (PropertyDescriptor propDesc: PropertyUtils.getPropertyDescriptors(bean)){
...
}
}
Contiene cioè un ciclo che scorre tutte le proprietà del bean. C'è una proprietà cui però non siamo interessati ed è la proprietà rappresentata dal metodo getClass
, metodo comune ad ogni oggetto poichè ereditato dalla classe Object
. All'interno del metodo readBean
viene quindi effettuato un controllo sul nome della proprietà e se corrisponde a "class" questa viene saltata:
String propName = propDesc.getName();
if (Arrays.asList(PROPS_TO_IGNORE).contains(propName)){
continue;
}
Si passa poi ad esaminare il tipo della proprietà:
Class propType = propDesc.getPropertyType();
In base al tipo dobbiamo scegliere la strategia con la quale interpretare l'input dell'utente e trasformarlo in un oggetto del tipo richiesto per la proprietà. Ad esempio se la proprietà è un numero intero possiamo accettare una stringa contenente solo cifre e convertirla in un numero.
Le conversioni verso i tipi primitivi ed alcune delle classi più comuni (Date
, URL
, BigDecimal
ed altre) sono già implementate in BeanUtils. Manca però un convertitore per gli enum
, questo è implementato nella classe EnumConverter
, posizionata all'interno di BeanConsoleIO
:
private static class EnumConverter implements Converter {
@Override
public Object convert(Class clazz, Object val) {
if (!(val instanceof String)){
throw new IllegalArgumentException("A String was expected");
}
if (!clazz.isEnum()){
throw new IllegalArgumentException("An enum class was expected");
}
String sVal = (String)val;
try {
return Enum.valueOf(clazz, sVal);
} catch (Exception e) {
throw new ConversionException(e);
}
}
}
Esaminiamo quindi come avviene la selezione del converter da utilizzare:
Converter converter = null;
if (propType.isEnum()) {
converter = new EnumConverter();
} else {
converter = convertUtils.lookup(propType);
if (converter==null) {
logger.warning("Skipping property "+propName
+" because there are no converters for type "
+propType);
continue;
}
}
E vediamo ora come la proprietà venga richiesta all'utente e l'input fornito analizzato tramite il converter:
System.out.print("* "+propName+" : ");
try {
boolean ok = false;
Object propValue = null;
do {
String s = in.readLine();
try {
propValue = converter.convert(propType, s);
ok = true;
} catch (ConversionException ce) {
System.err.println("Errore di conversione");
}
} while (!ok);
BeanUtils.setProperty(bean,propName, propValue);
} catch // ... gestione eccezioni
L'output delle proprietà del bean
Dopo aver realizzato il metodo di lettura dei bean passiamo al metodo di scrittura. La struttura del metodo writeBean
è simile a quella di readBean
: viene eseguito un ciclo sulle proprietà del bean.
In questo caso si riceve come parametro un elenco delle proprietà da evitare. In readBean veniva invece sempre e solo ignorata la proprietà class
. Nelle nostre applicazioni possiamo utilizzare uno di questi approcci o trovarne un terzo, più adatto alle nostre esigenze.
for (PropertyDescriptor propDesc : PropertyUtils.getPropertyDescriptors(bean)) {
String propName = propDesc.getName();
if (Arrays.asList(propsToIgnore).contains(propName)) {
continue;
}
L'ottenimento del valore da stampare è molto semplice:
propValue = PropertyUtils.getProperty(bean, propName);
propValueStr = propValue==null?"":propValue.toString();
Ad esclusione del valore null
stampiamo qualsiasi altro valore utilizzando il metodo toString
.
Prima di stampare il valore a video vengono eseguiti alcuni adattamenti per incolonnare i valori. Per il codice dei metodi addSpaces
e cutIfTooLong
è possibile fare riferimento al codice allegato all'articolo.
propName = cutIfTooLong(propName, FIELD_LEN);
System.out.println("* "+addSpaces(propName, FIELD_LEN)+" : "
+cutIfTooLong(propValueStr, FIELD_LEN));
Conclusioni
Avere una libreria in grado di analizzare le proprietà di un qualsiasi oggetto rende possibile realizzare applicazioni estremamente flessibili. Immaginiamo ad esempio di dover gestire il salvataggio ed il caricamento di un qualsiasi oggetto in un file XML e come potrebbe essere semplice realizzare delle classi in grado di soddisfare questa esigenza adattandosi a qualsiasi tipo di bean. Questa libreria all'apparenza così semplice permette, se bene usata, di eliminare molto del codice ripetitivo che si trova nelle applicazioni che sviluppiamo.