En guide til kortlægning med dozer

1. Oversigt

Dozer er en Java Bean til Java Bean mapper der rekursivt kopierer data fra et objekt til et andet, attribut for attribut.

Biblioteket understøtter ikke kun kortlægning mellem attributnavne på Java Beans, men også konverterer automatisk mellem typer - hvis de er forskellige.

De fleste konverteringsscenarier understøttes uden for boksen, men Dozer giver dig også mulighed for at angiv brugerdefinerede konverteringer via XML.

2. Enkelt eksempel

Lad os i vores første eksempel antage, at kilde- og destinationsdataobjekterne alle har de samme fælles attributnavne.

Dette er den mest basale kortlægning, man kan gøre med Dozer:

offentlig klasse Kilde {privat strengnavn; privat int alder public Source () {} public Source (String name, int age) {this.name = name; this.age = alder; } // standard getters og setters}

Så vores destinationsfil, Dest.java:

offentlig klasse Dest {private String name; privat int alder public Dest () {} public Dest (String name, int age) {this.name = name; this.age = alder; } // standard getters og setters}

Vi skal sørge for at inkluderer standard- eller nul-argumentkonstruktører, da Dozer bruger refleksion under emhætten.

Og lad os, med henblik på ydeevne, gøre vores kortlægger global og oprette et enkelt objekt, som vi bruger gennem vores tests:

DozerBeanMapper kortlægger; @Før offentligt ugyldigt før () kaster Undtagelse {mapper = ny DozerBeanMapper (); }

Lad os nu køre vores første test for at bekræfte, at når vi opretter en Kilde objekt, kan vi kortlægge det direkte på en Dest objekt:

@Test offentlig ugyldighed givenSourceObjectAndDestClass_whenMapsSameNameFieldsCorrectly_ thenCorrect () {Source source = new Source ("Baeldung", 10); Dest dest = mapper.map (kilde, Dest.klasse); assertEquals (dest.getName (), "Baeldung"); assertEquals (dest.getAge (), 10); }

Som vi kan se, bliver resultatet efter Dozer-kortlægningen en ny forekomst af Dest objekt, der indeholder værdier for alle felter, der har samme feltnavn som Kilde objekt.

Alternativt i stedet for at passere kortlægger det Dest klasse, kunne vi bare have oprettet Dest objekt og bestået kortlægger dens reference:

@Test offentlig ugyldighed givenSourceObjectAndDestObject_whenMapsSameNameFieldsCorrectly_ thenCorrect () {Source source = new Source ("Baeldung", 10); Dest dest = ny Dest (); mapper.map (kilde, dest); assertEquals (dest.getName (), "Baeldung"); assertEquals (dest.getAge (), 10); }

3. Maven-opsætning

Nu hvor vi har en grundlæggende forståelse af, hvordan Dozer fungerer, lad os tilføje følgende afhængighed af pom.xml:

 net.sf.dozer dozer 5.5.1 

Den seneste version er tilgængelig her.

4. Eksempel på datakonvertering

Som vi allerede ved, kan Dozer kortlægge et eksisterende objekt til et andet, så længe det finder attributter med samme navn i begge klasser.

Det er dog ikke altid tilfældet; og så, hvis en af ​​de kortlagte attributter er af forskellige datatyper, vil Dozer-kortlægningsmotoren gøre det udfører automatisk en datatypekonvertering.

Lad os se dette nye koncept i aktion:

offentlig klasse Kilde2 {privat streng-id; private dobbeltpoint; public Source2 () {} public Source2 (String id, double points) {this.id = id; this.points = point; } // standard getters og setters}

Og destinationsklassen:

offentlig klasse Dest2 {privat int id; private int-punkter; public Dest2 () {} public Dest2 (int id, int points) {super (); this.id = id; this.points = point; } // standard getters og setters}

Bemærk, at attributnavne er de samme men deres datatyper er forskellige.

I kildeklassen, id er en Snor og point er en dobbelt, hvorimod i destinationsklassen, id og point er begge heltals.

Lad os nu se, hvordan Dozer håndterer konverteringen korrekt:

@Test offentlig ugyldighed givenSourceAndDestWithDifferentFieldTypes_ whenMapsAndAutoConverts_thenCorrect () {Source2 kilde = ny Source2 ("320", 15.2); Dest2 dest = mapper.map (kilde, Dest2.class); assertEquals (dest.getId (), 320); assertEquals (dest.getPoints (), 15); }

Vi gik forbi “320” og 15.2, a Snor og en dobbelt ind i kildeobjektet, og resultatet havde 320 og 15, begge heltals i destinationsobjektet.

5. Grundlæggende brugerdefinerede tilknytninger via XML

I alle de tidligere eksempler, vi har set, har både kilde- og destinationsdataobjekter de samme feltnavne, hvilket giver mulighed for let kortlægning på vores side.

Imidlertid vil der i utallige applikationer være utallige gange, hvor de to dataobjekter, vi kortlægger, ikke har felter, der har et fælles ejendomsnavn.

For at løse dette giver Dozer os en mulighed for at oprette en tilpasset kortlægningskonfiguration i XML.

I denne XML-fil kan vi definere klassetilknytningsposter, som Dozer-kortlægningsmotoren vil bruge til at bestemme, hvilken kildeattribut der skal kortlægges til hvilken destinationsattribut.

Lad os se på et eksempel, og lad os prøve at afvise dataobjekter fra en applikation bygget af en fransk programmør til en engelsk måde at navngive vores objekter på.

Vi har en Person objekt med navn, kaldenavn og alder felter:

offentlig klasse person {privat strengnavn; privat streng kaldenavn; privat int alder offentlig person () {} offentlig person (strengnavn, streng kaldenavn, int alder) {super (); dette.navn = navn; dette. kaldenavn = kaldenavn; this.age = alder; } // standard getters og setters}

Det objekt, vi ikke afskalder, hedder Personne og har felter nom, surnom og alder:

offentlig klasse Personne {private String nom; private String surnom; privat int alder public Personne () {} public Personne (String nom, String surnom, int age) {super (); this.nom = nom; this.surnom = surnom; this.age = alder; } // standard getters og setters}

Disse objekter opnår virkelig det samme formål, men vi har en sprogbarriere. For at hjælpe med denne barriere kan vi bruge Dozer til at kortlægge franskmændene Personne modsætter sig vores Person objekt.

Vi skal kun oprette en brugerdefineret kortlægningsfil for at hjælpe Dozer med at gøre dette, vi kalder det dozer_mapping.xml:

   com.baeldung.dozer.Personne com.baeldung.dozer.Person nom navn   surnom kaldenavn

Dette er det enkleste eksempel på en brugerdefineret XML-kortfil, vi kan have.

For nu er det nok at bemærke, at vi har det som vores rodelement, som har et barn , vi kan have så mange af disse børn indeni da der er forekomster af klassepar, der har brug for tilpasset kortlægning.

Bemærk også, hvordan vi specificerer kilde- og destinationsklasser inde i tags. Dette efterfølges af en for hvert kilde- og destinationsfeltpar, der har brug for tilpasset kortlægning.

Endelig bemærk, at vi ikke har inkluderet feltet alder i vores brugerdefinerede kortlægningsfil. Det franske ord for alder er stadig alder, hvilket bringer os til et andet vigtigt træk ved Dozer.

Egenskaber, der har samme navn, behøver ikke at blive specificeret i XML-filens tilknytning. Dozer kortlægger automatisk alle felter med det samme egenskabsnavn fra kildeobjektet til destinationsobjektet.

Vi placerer derefter vores brugerdefinerede XML-fil på klassestien direkte under src folder. Uanset hvor vi placerer den på klassestien, søger Dozer dog hele klassestien på udkig efter den angivne fil.

Lad os oprette en hjælpemetode til at tilføje kortlægningsfiler til vores kortlægger:

public void configureMapper (String ... mappingFileUrls) {mapper.setMappingFiles (Arrays.asList (mappingFileUrls)); }

Lad os nu teste koden:

@Test offentlig ugyldighed givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_ whenMaps_thenCorrect () {configureMapper ("dozer_mapping.xml"); Personne frenchAppPerson = ny Personne ("Sylvester Stallone", "Rambo", 70); Person englishAppPerson = mapper.map (franskAppPerson, Person.class); assertEquals (englishAppPerson.getName (), frenchAppPerson.getNom ()); assertEquals (englishAppPerson.getNickname (), franskAppPerson.getSurnom ()); assertEquals (englishAppPerson.getAge (), franskAppPerson.getAge ()); }

Som vist i testen, DozerBeanMapper accepterer en liste med brugerdefinerede XML-kortlægningsfiler og beslutter, hvornår de skal bruges under kørsel.

Forudsat at vi nu begynder at fjerne disse dataobjekter frem og tilbage mellem vores engelske app og den franske app. Vi behøver ikke oprette en anden kortlægning i XML-filen, Dozer er smart nok til at kortlægge objekterne begge veje med kun en kortlægningskonfiguration:

@Test offentlig ugyldighed givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_ whenMapsBidirectionally_thenCorrect () {configureMapper ("dozer_mapping.xml"); Person englishAppPerson = ny person ("Dwayne Johnson", "The Rock", 44); Personne frenchAppPerson = mapper.map (engelskAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), engelskAppPerson.getAge ()); }

Og så bruger denne eksemplet test denne anden funktion i Dozer - det faktum, at Dozer-kortlægningsmotoren er tovejs, så hvis vi vil kortlægge destinationsobjektet til kildeobjektet, behøver vi ikke tilføje en anden klassekortlægning til XML-filen.

Vi kan også indlæse en brugerdefineret kortlægningsfil uden for klassestien, hvis det er nødvendigt, brugfil:”Præfiks i ressourcenavnet.

I et Windows-miljø (såsom testen nedenfor) bruger vi selvfølgelig den Windows-specifikke filsyntaks.

På en Linux-boks gemmer vi muligvis filen under /hjem og så:

configureMapper ("fil: /home/dozer_mapping.xml");

Og på Mac OS:

configureMapper ("file: /Users/me/dozer_mapping.xml");

Hvis du kører enhedstestene fra github-projektet (som du burde), kan du kopiere kortlægningsfilen til den relevante placering og ændre input for configureMapper metode.

Kortlægningsfilen er tilgængelig under test / ressourcemappen i GitHub-projektet:

@Test offentlig ugyldighed givenMappingFileOutsideClasspath_whenMaps_thenCorrect () {configureMapper ("fil: E: \ dozer_mapping.xml"); Person englishAppPerson = ny person ("Marshall Bruce Mathers III", "Eminem", 43); Personne frenchAppPerson = mapper.map (engelskAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), engelskAppPerson.getAge ()); }

6. Jokertegn og yderligere XML-tilpasning

Lad os oprette en anden tilpasset kortfil kaldet dozer_mapping2.xml:

   com.baeldung.dozer.Personne com.baeldung.dozer.Person nom navn   surnom kaldenavn

Bemærk, at vi har tilføjet en attribut jokertegn til element, der ikke var der før.

Som standard, jokertegn er rigtigt. Det fortæller Dozer-motoren, at vi ønsker, at alle felter i kildeobjektet skal kortlægges til deres relevante destinationsfelter.

Når vi indstiller det til falsk, vi fortæller Dozer kun at kortlægge felter, som vi eksplicit har angivet i XML.

Så i ovenstående konfiguration vil vi kun kortlægge to felter, der udelades alder:

@ Test offentlig ugyldighed givenSrcAndDest_whenMapsOnlySpecifiedFields_thenCorrect () {configureMapper ("dozer_mapping2.xml"); Person englishAppPerson = ny person ("Shawn Corey Carter", "Jay Z", 46); Personne frenchAppPerson = mapper.map (engelskAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), 0); }

Som vi kan se i den sidste påstand, destinationen alder felt forblev 0.

7. Tilpasset kortlægning via kommentarer

Til enkle kortlægningssager og tilfælde, hvor vi også har skriveadgang til de dataobjekter, vi gerne vil kortlægge, behøver vi muligvis ikke at bruge XML-kortlægning.

Kortlægning af forskellige navngivne felter via annoteringer er meget enkel, og vi er nødt til at skrive meget mindre kode end i XML-kortlægning, men kan kun hjælpe os i enkle tilfælde.

Lad os replikere vores dataobjekter til Person2.java og Personne2.java uden at ændre markerne overhovedet.

For at implementere dette behøver vi kun tilføje @kortlægger ("destinationFieldName") kommentar på getter metoder i kildeobjektet. Ligesom:

@Mapping ("navn") offentlig String getNom () {return nom; } @Mapping ("kaldenavn") offentlig String getSurnom () {return surnom; }

Denne gang behandler vi Personne2 som kilde, men det betyder ikke noget på grund af tovejs natur af bulldozermotoren.

Nu med al XML-relateret kode fjernet, er vores testkode kortere:

@Test offentlig ugyldighed givenAnnotatedSrcFields_whenMapsToRightDestField_thenCorrect () {Person2 englishAppPerson = ny Person2 ("Jean-Claude Van Damme", "JCVD", 55); Personne2 frenchAppPerson = mapper.map (engelskAppPerson, Personne2.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), engelskAppPerson.getAge ()); }

Vi kan også teste for tovejs:

@Test offentlig ugyldighed givenAnnotatedSrcFields_whenMapsToRightDestFieldBidirectionally_ thenCorrect () {Personne2 frenchAppPerson = new Personne2 ("Jason Statham", "transporter", 49); Person2 englishAppPerson = mapper.map (franskAppPerson, Person2.class); assertEquals (englishAppPerson.getName (), frenchAppPerson.getNom ()); assertEquals (englishAppPerson.getNickname (), franskAppPerson.getSurnom ()); assertEquals (englishAppPerson.getAge (), franskAppPerson.getAge ()); }

8. Tilpasset API-kortlægning

I vores tidligere eksempler, hvor vi afmonterer dataobjekter fra en fransk applikation, brugte vi XML og annoteringer til at tilpasse vores kortlægning.

Et andet alternativ tilgængeligt i Dozer, der ligner annotationskortlægning, er API-kortlægning. De ligner hinanden, fordi vi fjerner XML-konfiguration og strengt bruger Java-kode.

I dette tilfælde bruger vi BeanMappingBuilder klasse, defineret i vores enkleste tilfælde som sådan:

BeanMappingBuilder builder = ny BeanMappingBuilder () {@Override beskyttet ugyldig konfiguration () {mapping (Person.class, Personne.class) .fields ("name", "nom") .fields ("nickname", "surnom"); }};

Som vi kan se, har vi en abstrakt metode, konfigurer (), som vi skal tilsidesætte for at definere vores konfigurationer. Så ligesom vores tags i XML definerer vi så mange TypeMappingBuilders som vi har brug for.

Disse bygherrer fortæller Dozer, hvilken kilde til destinationsfelter vi kortlægger. Vi passerer derefter BeanMappingBuilder til DozerBeanMapper som vi ville, XML-kortlægningsfilen kun med en anden API:

@Test offentlig ugyldighed givenApiMapper_whenMaps_thenCorrect () {mapper.addMapping (builder); Personne frenchAppPerson = ny Personne ("Sylvester Stallone", "Rambo", 70); Person englishAppPerson = mapper.map (franskAppPerson, Person.class); assertEquals (englishAppPerson.getName (), frenchAppPerson.getNom ()); assertEquals (englishAppPerson.getNickname (), franskAppPerson.getSurnom ()); assertEquals (englishAppPerson.getAge (), franskAppPerson.getAge ()); }

Kortlægnings-API'en er også tovejs:

@Test offentlig ugyldighed givenApiMapper_whenMapsBidirectionally_thenCorrect () {mapper.addMapping (builder); Person englishAppPerson = ny person ("Sylvester Stallone", "Rambo", 70); Personne frenchAppPerson = mapper.map (engelskAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), engelskAppPerson.getAge ()); }

Eller vi kan vælge kun at kortlægge eksplicit specificerede felter med denne builderkonfiguration:

BeanMappingBuilder builderMinusAge = ny BeanMappingBuilder () {@ Override beskyttet ugyldig konfigurere () {mapping (Person.class, Personne.class) .fields ("name", "nom") .fields ("nickname", "surnom"). Eksklud ("alder"); }};

og vores alder == 0 testen er tilbage:

@Test offentligt ugyldigt givetApiMapper_whenMapsOnlySpecifiedFields_thenCorrect () {mapper.addMapping (builderMinusAge); Person englishAppPerson = ny person ("Sylvester Stallone", "Rambo", 70); Personne frenchAppPerson = mapper.map (engelskAppPerson, Personne.class); assertEquals (frenchAppPerson.getNom (), englishAppPerson.getName ()); assertEquals (frenchAppPerson.getSurnom (), englishAppPerson.getNickname ()); assertEquals (frenchAppPerson.getAge (), 0); }

9. Brugerdefinerede konvertere

Et andet scenarie, vi måske står over for ved kortlægning, er, hvor vi gerne vil udføre tilpasset kortlægning mellem to objekter.

Vi har set på scenarier, hvor kilde- og destinationsfeltnavne er forskellige som på fransk Personne objekt. Dette afsnit løser et andet problem.

Hvad hvis et dataobjekt, vi ikke afmonterer, repræsenterer et dato- og tidsfelt som f.eks lang eller Unix-tid som sådan:

1182882159000

Men vores eget ækvivalente dataobjekt repræsenterer samme dato og tidsfelt og værdi i dette ISO-format som f.eks Snor:

2007-06-26T21: 22: 39Z

Standardkonverteren ville simpelthen kortlægge den lange værdi til a Snor ligesom:

"1182882159000"

Dette ville helt sikkert bug vores app. Så hvordan løser vi dette? Vi løser det ved tilføje en konfigurationsblok i kortlægning af XML-filen og specificerer vores egen konverter.

Lad os først replikere fjernapplikationens Person DTO med en navn, derefter fødselsdato og -tidspunkt dtob Mark:

offentlig klasse Personne3 {privat strengnavn; privat lang dtob; offentlig Personne3 (strengnavn, lang dtob) {super (); dette.navn = navn; this.dtob = dtob; } // standard getters og setters}

og her er vores egen:

offentlig klasse Person3 {privat strengnavn; private String dtob; offentlig person3 (strengnavn, streng dtob) {super (); dette.navn = navn; this.dtob = dtob; } // standard getters og setters}

Læg mærke til typen forskel på dtob i kilde- og destinations-DTO'erne.

Lad os også oprette vores egne CustomConverter at overføre til Dozer i XML-kortlægningen:

offentlig klasse MyCustomConvertor implementerer CustomConverter {@ Override public Object convert (Object dest, Object source, Class arg2, Class arg3) {if (source == null) return null; hvis (kildeinstans af Personne3) {Personne3 person = (Personne3) kilde; Dato dato = ny dato (person.getDtob ()); DateFormat format = nyt SimpleDateFormat ("åååå-MM-dd'T'HH: mm: ss'Z '"); Streng isoDate = format.format (dato); returner ny Person3 (person.getName (), isoDate); } ellers hvis (kildeinstans af Person3) {Person3 person = (Person3) kilde; DateFormat format = nyt SimpleDateFormat ("åååå-MM-dd'T'HH: mm: ss'Z '"); Dato dato = format.parse (person.getDtob ()); lang tidsstempel = date.getTime (); returner ny Personne3 (person.getName (), tidsstempel); }}}

Vi skal kun tilsidesætte konvertere() metode derefter returnere hvad vi vil vende tilbage til det. Vi får brug for kilde- og destinationsobjekterne og deres klassetyper.

Læg mærke til, hvordan vi har taget os af tovejsretning ved at antage, at kilden kan være en af ​​de to klasser, vi kortlægger.

Vi opretter en ny kortfil for klarhedens skyld, dozer_custom_convertor.xml:

     com.baeldung.dozer.Personne3 com.baeldung.dozer.Person3 

Dette er den normale kortlægningsfil, vi har set i de foregående sektioner, vi har kun tilføjet en blok, inden for hvilken vi kan definere så mange brugerdefinerede konvertere, som vi har brug for med deres respektive kilde- og destinationsdataklasser.

Lad os teste vores nye CustomConverter kode:

@Test offentligt ugyldigt givenSrcAndDestWithDifferentFieldTypes_whenAbleToCustomConvert_ thenCorrect () {configureMapper ("dozer_custom_convertor.xml"); String dateTime = "2007-06-26T21: 22: 39Z"; lang tidsstempel = ny Lang ("1182882159000"); Person3 person = ny Person3 ("Rich", dateTime); Personne3 person0 = mapper.map (person, Personne3.class); assertEquals (tidsstempel, person0.getDtob ()); }

Vi kan også teste for at sikre, at det er tovejs:

@Test offentlig ugyldighed givenSrcAndDestWithDifferentFieldTypes_ whenAbleToCustomConvertBidirectionally_thenCorrect () {configureMapper ("dozer_custom_convertor.xml"); String dateTime = "2007-06-26T21: 22: 39Z"; lang tidsstempel = ny Lang ("1182882159000"); Personne3 person = ny Personne3 ("Rich", tidsstempel); Person3 person0 = mapper.map (person, Person3.class); assertEquals (dateTime, person0.getDtob ()); }

10. Konklusion

I denne vejledning har vi det introducerede det meste af det grundlæggende i Dozer Mapping-biblioteket og hvordan man bruger det i vores applikationer.

Den fulde implementering af alle disse eksempler og kodestykker findes i Dozer github-projektet.