Sono diversi i motivi per cui in una applicazione si ha la necessità di effettuare la copia di un bean. Ad esempio potrebbe essere necessario integrarsi con sistemi esterni e non si vuole esporre l’intero oggetto perché alcune proprietà non sono necessarie o devono rimanere riservate (si pensi alla password di un utente). Oppure l’oggetto sorgente potrebbe non essere serializzabile. Oppure ancora potrebbe essere caratterizzato da molte proprietà, sia di tipo primitivo che complesse, generando bean annidati con profondità non trascurabile.
In queste situazioni l’esecuzione della copia del bean può essere fatta programmando un convertitore apposito per ogni bean da convertire oppure utilizzando un framework, come ad esempio Dozer, semplificando notevolmente la generazione del codice. Dozer è un framework di mapping JavaBean-to-JavaBean che esegue la copia in modo ricorsivo degli oggetti, eseguendo, quando necessario, la conversione automatica tra tipi. Il tutto utilizzando un approccio dichiarativo per mezzo di un file XML di configurazione (quando necessario).
Un Primo Semplice Progetto
Utilizziamo un semplice progetto java di test generato con Maven senza selezionare alcun archetype ed inseriamo la dipendenza a Dozer.
1 2 3 4 5 6 |
<dependencies> <dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.4.0</version> </dependency> |
Implementiamo quindi le due classi sorgente e destinazione del mapping, per ora avendo cura di mantenere lo stesso nome per le proprietà ma utilizzando tipi differenti:
|
|
||||
|
|
Per semplicità non sono state riportati i costruttori ed i metodi toString()
delle quattro classi che comunque sono stati generati con i relativi tool di eclipse e che troverete nel codice sorgente. Rammentare comunque che tutte e quattro el classi devono sempre avere un costruttore senza parametri. Altrimenti verrà sollevata una eccezione del tipo:
Exception in thread "main" org.dozer.MappingException: java.lang.NoSuchMethodException: it.javaboss.model.Destination.<init>()
Implementiamo quindi la classe DozerMain
contenente il main per l’esecuzione del mapping:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class DozerMain { public static void main(String[] args) { Mapper mapper = new DozerBeanMapper(); Source sc = new Source( "1", "2", "3", "s->d", new SubSource( 10, true ) ); Destination dc = mapper.map( sc, Destination.class); System.out.println( "Source -> Destination" ); System.out.println( sc.toString() ); System.out.println( dc.toString() ); Destination dc1 = new Destination( 1, 2L, 3, "d->s", new SubDestination( 20, false ) ); Source sc1 = mapper.map( dc1, Source.class ); System.out.println( "Destination -> Source" ); System.out.println( dc1.toString() ); System.out.println( sc1.toString() ); } } |
1 2 3 4 5 6 7 8 9 10 11 |
Source -> Destination Source [sourceStringForInteger=1, sourceStringForLong=2, sourceStringForint=3, sourceStringForString=s->d, subSource=SubSource [property1=10, property2=true]] Destination [sourceStringForInteger=1, sourceStringForLong=2, sourceStringForint=3, sourceStringForString=s->d, subDestination=null] Destination -> Source Destination [sourceStringForInteger=1, sourceStringForLong=2, sourceStringForint=3, sourceStringForString=d->s, subDestination=SubDestination [property1=20, property2=false]] Source [sourceStringForInteger=1, sourceStringForLong=2, sourceStringForint=3, sourceStringForString=d->s, subSource=null] |
Dall’analisi dell’output si nota che tutte le proprietà con lo stesso nome nelle due classi non solo sono state correttamente mappate ma hanno anche subito la conversione del tipo in entrambe le direzioni della conversione. Le proprietà subDestination
e subSource
. invece, non sono state mappate avendo nomi differenti.
Mapping via XML
Per situazioni più complesse di mapping è necessario utilizzare un file XML descrittivo di corrispondenza tra le proprietà delle due classi oggetto del mapping. Il file è ovviamente valido sia in un senso del mapping che nel senso opposto. Creiamo quindi il file mapping.xml
e posizioniamolo nella cartella resources
del nostro progetto Mavem. Il file avrà il seguente aspetto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?xml version="1.0" encoding="UTF-8"?> <mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://dozer.sourceforge.net http://dozer.sourceforge.net/schema/beanmapping.xsd"> <mapping> <class-a>it.javaboss.model.Source</class-a> <class-b>it.javaboss.model.Destination</class-b> <field> <a>subSource</a> <b>subDestination</b> </field> </mapping> </mappings> |
Il costruttore della classe DozerBeanMapper
accetta come parametro un array di stringhe corrispondenti ai nomi dei file di mapping, che verranno ricercati dal framework in tutto il classpath del progetto. L’inizializzazione della classe per il nostro esempio sarà quindi:
1 |
Mapper mapper = new DozerBeanMapper( Arrays.asList( "mapping.xml" ) ); |
Utilizzo dell’IoC
In generale è preferibile avere una istanza del Dozer mapper per VM. Tale situazione è realizzabile in modo semplice utilizzando un framework IoC, come ad esempio Spring, opportunamente configurato in modo da istanziare il mapper in modalità singleton. Eventualmente il framework dispone di una classe di utilità che restituisce il singleton attraverso l’esecuzione di DozerBeanMapperSingletonWrapper.getInstance()
.
Codice Sorgente
Il codice sorgente dell’esempio e scaricabile qui dozer.