Nell’articolo Esportazione dati tramite annotation in Java abbiamo introdotto un framework per l’esportazione di dati in formato Excel e TXT mediante semplici annotation. Se siamo però interessati ad un framework più maturo la scelta non può cadere che su BeanIO, una libreria per il marshalling e l’unmarshalling di bean da uno stream, che supporta i formati di XML, CSV, delimitati e a lunghezza fissa e che si integra perfettamente nell’architettura di Spring Batch.
Esempio Base
Per comprendere i diversi aspetti del framework iniziamo con la realizzazione di un semplice esempio che poi estenderemo di volta in volta introducendo i diversi aspetti della libreria. Per prima cosa creiamo quindi un progetto Maven ed inseriamo la dipendenza al framework:
1 2 3 4 5 |
dependency> <groupId>org.beanio</groupId> <artifactId>beanio</artifactId> <version>2.1.0.M1</version> </dependency> |
Quindi introduciamo nel progetto le classi Customer
e Address
che saranno i bean che vogliamo serializzare e deserializzare.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Customer { private String firstName; private String lastName; private Date acquisition; private Address address; /* Getters and Setters */ } public class Address { String street; String city; String state; String zip; /* Getters and Setters */ } |
Configurazione
Il comportamento di BeanIO può essere configurato mediante file XML, annotazioni o anche builder API. Per gli scopi di questo tutorial è preferibile utilizzare l’XML perchè a mio avviso è un metodo più completo e flessibile. Creiamo quindi nella cartella resources
del progetto il file customer-mapping.xml
che conterrà il seguente XML.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<beanio xmlns="http://www.beanio.org/2012/03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.beanio.org/2012/03 http://www.beanio.org/2012/03/mapping.xsd"> <stream name="customerFile" format="csv"> <record name="customer" class="it.javaboss.bean.Customer"> <field name="firstName" /> <field name="lastName" /> <field name="acquisition" format="dd/MM/yyyy" /> <segment name="address" class="it.javaboss.bean.Address"> <field name="street" /> <field name="city" /> <field name="state" /> <field name="zip" /> </segment> </record> </stream> </beanio> |
<stream> |
Questa sezione descrive il componente base utilizzati da BeanIO per mappare un flusso di input o una String in un oggetto Java. Possono essere presenti più tag Il formato dello stream è invece definito dall’attributo
|
||
<record> |
Ogni record letto o scritto sullo stream deve essere mappato attraverso il tag <record> . Nel file deve esserne presente almeno uno ed è utilizzato dal framework per validate il record ma soprattutto per eseguire il binding tra i valori del record e le proprietà del bean cui è mappato attraverso il tag <field> . Nell’esempio la classe del bean mappato è specificata nella proprietà class , ed assume il valore it.javaboss.bean.Customer , mentre le proprietà primitive sono da utilizzare sono indicate nella proprietà name del tag <field> . Nel caso del tipo Date è anche possibile specificare il formato della data. |
||
<segment> |
Questa sezione serve a mappare un gruppo di campi in uno specifico record ed è utile, ad esempio, per indicare che i campi appartengono ad un oggetto annidato, come è il caso dell’oggetto Address dell’esempio. Naturalmente la proprietà class indica il tipo del bean annidato. |
Il file di configurazione è letto mediante la classe StreamFactory, la quale è poi utilizzata, come vedremo in seguito, per generare istanze di oggetti BeanReader
, BeanWriter
, Marshaller
e Unmarshaller
.
1 2 3 4 5 6 |
// create a StreamFactory StreamFactory factory = StreamFactory.newInstance(); // load the mapping file factory.load( new File( CustomerReader.class.getClassLoader().getResource("customer-mapping.xml").getFile() ) ); |
Marshalling e Unmarshalling
Veniamo alla operazione più semplice che è possibile eseguire col framework che è quella di serializzare e deserializzare un singolo record. A partire dalla classe factory generata sopra invochiamo il metodo createMarshaller()
per generare un’istanza dell’interfaccia Marshaller
indicando il nome stream definito nel file XML. Su tale oggetto è poi possibile invocare il metodo marshal()
sul bean per ottenere lo stream di output.
1 2 3 |
String record = factory.createMarshaller("customerFile") .marshal( CustomerUtils.JENNIFER_JONES ).toString(); System.out.println( record ); |
Si noti che la classe di utilità CustomerUtils
definisce diverse costanti accessibili in modo statico conteneti istanze dei bean Customer
e Address
utilizzati. L’output prodotto, nel caso di stream csv
, sarà:
Jennifer,Jones,05/02/2019,44 West 29th Street,New York,NY,10001
E’ possibile poi procedere con l’unmarshal dello stesso stream generato questa volta creando un oggetto Unmarshaller
dalla classe factory, con le medesime modalità viste sopra, ed invocando su di esso il metodo unmarshal() sul record generato.
1 2 3 |
Customer customer = (Customer) factory.createUnmarshaller("customerFile") .unmarshal( record ); System.out.println( "Customer :" + customer.getLastName() ); |
BeanWriter e BeanReader
In modo assolutamente analogo possiamo generare le classi per la scrittura dello stream su file, nel formato specificato, utilizzando istanze degli oggetti BeanReader
, BeanWriter
. Il codice per scrivere un csv
su file è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class CustomerWriter { public static void main(String[] args) throws Exception { // create a StreamFactory StreamFactory factory = StreamFactory.newInstance(); // load the mapping file factory.load( new File( CustomerReader.class.getClassLoader() .getResource("customer-mapping.xml").getFile() ) ); // use a StreamFactory to create a BeanWriter BeanWriter out = factory.createWriter("customerFile", new File( "src/main/resources/customers.csv") ); // write an Employee object directly to the BeanWriter for ( Customer customer : CustomerUtils.CUSTOMERS ) { out.write( customer ); } out.flush(); out.close(); } } |
customers.csv
contenente i dati:
Jennifer,Jones,05/02/2019,44 West 29th Street,New York,NY,10001
Paul,Adams,05/02/2019,29 West 44th Street,New York,NY,10001
Analogamente il codice sorgente per leggere un file csv
e deserializzarlo è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class CustomerReader { public static void main(String[] args) throws Exception { // create a StreamFactory StreamFactory factory = StreamFactory.newInstance(); // load the mapping file factory.load( new File( CustomerReader.class.getClassLoader().getResource("customer-mapping.xml").getFile() ) ); // use a StreamFactory to create a BeanReader BeanReader in = factory.createReader("customerFile", new File( CustomerReader.class.getClassLoader().getResource("customers.csv").getFile() ) ); Customer customer; while ((customer = (Customer) in.read()) != null) { // process the customer... System.out.println( customer.getFirstName() + " " + customer.getLastName() ); } in.close(); } } |
Codice Sorgente
Il codice sorgente con l’esempio presentato è scaricabile qui bean-io.