Eine Anleitung zur Zuordnung mit Dozer

Eine Anleitung zum Mappen mit Dozer

1. Überblick

Dozer ist einJava Bean to Java Bean mapper, der rekursiv Daten von Attribut zu Attribut von einem Objekt zu einem anderen kopiert.

Die Bibliothek unterstützt nicht nur die Zuordnung zwischen Attributnamen von Java Beans, sondern auchautomatically converts between types - sofern diese unterschiedlich sind.

Die meisten Konvertierungsszenarien werden sofort unterstützt, aber Dozer ermöglicht Ihnen auchspecify custom conversions via XML.

2. Einfaches Beispiel

In unserem ersten Beispiel nehmen wir an, dass die Quell- und Zieldatenobjekte alle dieselben gemeinsamen Attributnamen haben.

Dies ist das grundlegendste Mapping, das man mit Dozer machen kann:

public class Source {
    private String name;
    private int age;

    public Source() {}

    public Source(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // standard getters and setters
}

Dann unsere ZieldateiDest.java:

public class Dest {
    private String name;
    private int age;

    public Dest() {}

    public Dest(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // standard getters and setters
}

Wir müssen aufinclude the default or zero argument constructors achten, da Dozer Reflexion unter der Haube verwendet.

Lassen Sie uns aus Leistungsgründen unseren Mapper global machen und ein einzelnes Objekt erstellen, das wir während unserer Tests verwenden werden:

DozerBeanMapper mapper;

@Before
public void before() throws Exception {
    mapper = new DozerBeanMapper();
}

Führen Sie nun unseren ersten Test durch, um zu bestätigen, dass wir beim Erstellen einesSource-Objekts dieses direkt einemDest-Objekt zuordnen können:

@Test
public void givenSourceObjectAndDestClass_whenMapsSameNameFieldsCorrectly_
  thenCorrect() {
    Source source = new Source("example", 10);
    Dest dest = mapper.map(source, Dest.class);

    assertEquals(dest.getName(), "example");
    assertEquals(dest.getAge(), 10);
}

Wie wir sehen können, ist das Ergebnis nach der Dozer-Zuordnung eine neue Instanz desDest-Objekts, die Werte für alle Felder enthält, die denselben Feldnamen wie dasSource-Objekt haben.

Anstattmapper dieDest-Klasse zu übergeben, hätten wir alternativ einfach dasDest-Objekt erstellen undmapper seine Referenz übergeben können:

@Test
public void givenSourceObjectAndDestObject_whenMapsSameNameFieldsCorrectly_
  thenCorrect() {
    Source source = new Source("example", 10);
    Dest dest = new Dest();
    mapper.map(source, dest);

    assertEquals(dest.getName(), "example");
    assertEquals(dest.getAge(), 10);
}

3. Maven Setup

Nachdem wir nun ein grundlegendes Verständnis der Funktionsweise von Dozer haben, fügen wir denpom.xml die folgende Abhängigkeit hinzu:


    net.sf.dozer
    dozer
    5.5.1

Die neueste Version isthere verfügbar.

4. Beispiel für die Datenkonvertierung

Wie wir bereits wissen, kann Dozer ein vorhandenes Objekt einem anderen zuordnen, sofern es in beiden Klassen Attribute mit demselben Namen findet.

Dies ist jedoch nicht immer der Fall. Wenn eines der zugeordneten Attribute unterschiedliche Datentypen aufweist, gibt die Dozer-Zuordnungs-Engineautomatically perform a data type conversion aus.

Lassen Sie uns dieses neue Konzept in Aktion sehen:

public class Source2 {
    private String id;
    private double points;

    public Source2() {}

    public Source2(String id, double points) {
        this.id = id;
        this.points = points;
    }

    // standard getters and setters
}

Und die Zielklasse:

public class Dest2 {
    private int id;
    private int points;

    public Dest2() {}

    public Dest2(int id, int points) {
        super();
        this.id = id;
        this.points = points;
    }

    // standard getters and setters
}

Beachten Sie, dass die Attributnamen identisch sind, jedochtheir data types are different.

In der Quellklasse istid einString undpoints ist eindouble, während in der Zielklasseid undpoints beide sind integers.

Lassen Sie uns nun sehen, wie Dozer die Konvertierung korrekt handhabt:

@Test
public void givenSourceAndDestWithDifferentFieldTypes_
  whenMapsAndAutoConverts_thenCorrect() {
    Source2 source = new Source2("320", 15.2);
    Dest2 dest = mapper.map(source, Dest2.class);

    assertEquals(dest.getId(), 320);
    assertEquals(dest.getPoints(), 15);
}

Wir haben“320” und15.2, aString unddouble an das Quellobjekt übergeben, und das Ergebnis hatte320 und15,, beideinteger) ss im Zielobjekt.

5. Grundlegende benutzerdefinierte Zuordnungen über XML

In allen vorherigen Beispielen haben sowohl das Quell- als auch das Zieldatenobjekt die gleichen Feldnamen, was eine einfache Zuordnung auf unserer Seite ermöglicht.

In realen Anwendungen wird es jedoch unzählige Male geben, in denen die beiden Datenobjekte, die wir zuordnen, keine Felder haben, die einen gemeinsamen Eigenschaftsnamen haben.

Um dies zu lösen, gibt uns Dozer die Möglichkeit,custom mapping configuration in XML zu erstellen.

In dieser XML-Datei können Sie Klassenzuordnungseinträge definieren, anhand derer die Dozer-Zuordnungsengine entscheidet, welches Quellattribut welchem ​​Zielattribut zugeordnet werden soll.

Schauen wir uns ein Beispiel an und versuchen wir, die Zuordnung von Datenobjekten aus einer von einem französischen Programmierer erstellten Anwendung zu einem englischen Stil für die Benennung unserer Objekte aufzuheben.

Wir haben einPerson-Objekt mit den Feldernname,nickname undage:

public class Person {
    private String name;
    private String nickname;
    private int age;

    public Person() {}

    public Person(String name, String nickname, int age) {
        super();
        this.name = name;
        this.nickname = nickname;
        this.age = age;
    }

    // standard getters and setters
}

Das Objekt, das wir nicht bereitstellen, heißtPersonne und hat die Feldernom,surnom undage:

public class Personne {
    private String nom;
    private String surnom;
    private int age;

    public Personne() {}

    public Personne(String nom, String surnom, int age) {
        super();
        this.nom = nom;
        this.surnom = surnom;
        this.age = age;
    }

    // standard getters and setters
}

Diese Objekte erfüllen wirklich den gleichen Zweck, aber wir haben eine Sprachbarriere. Um diese Barriere zu überwinden, können wir Dozer verwenden, um das französischePersonne-Objekt unseremPerson-Objekt zuzuordnen.

Wir müssen nur eine benutzerdefinierte Zuordnungsdatei erstellen, um Dozer dabei zu unterstützen. Wir nennen siedozer_mapping.xml:



    
        com.example.dozer.Personne
        com.example.dozer.Person
        
            nom
            name
        
        
            surnom
            nickname
        
    

Dies ist das einfachste Beispiel für eine benutzerdefinierte XML-Zuordnungsdatei, die wir haben können.

Im Moment reicht es zu bemerken, dass wir<mappings> als unser Stammelement haben, das ein Kind<mapping> hat. Wir können so viele dieser Kinder in<mappings> haben, wie es Klassenvorfälle gibt Paare, die eine benutzerdefinierte Zuordnung benötigen.

Beachten Sie auch, wie wir die Quell- und Zielklassen in den<mapping></mapping>-Tags angeben. Darauf folgt ein<field></field> für jedes Quell- und Zielfeldpaar, das eine benutzerdefinierte Zuordnung benötigt.

Beachten Sie schließlich, dass wir das Feldage nicht in unsere benutzerdefinierte Zuordnungsdatei aufgenommen haben. Das französische Wort für Alter ist immer noch Alter, was uns zu einem weiteren wichtigen Merkmal von Dozer bringt.

Properties that are of the same name do not need to be specified in the mapping XML file. Dozer ordnet automatisch alle Felder mit demselben Eigenschaftsnamen vom Quellobjekt zum Zielobjekt zu.

Wir werden dann unsere benutzerdefinierte XML-Datei im Klassenpfad direkt unter dem Ordnersrcablegen. Wo immer wir es auf dem Klassenpfad platzieren, durchsucht Dozer den gesamten Klassenpfad nach der angegebenen Datei.

Erstellen wir eine Hilfsmethode, um unserenmapper Zuordnungsdateien hinzuzufügen:

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

Testen wir nun den Code:

@Test
public void givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_
  whenMaps_thenCorrect() {
    configureMapper("dozer_mapping.xml");
    Personne frenchAppPerson = new Personne("Sylvester Stallone", "Rambo", 70);
    Person englishAppPerson = mapper.map(frenchAppPerson, Person.class);

    assertEquals(englishAppPerson.getName(), frenchAppPerson.getNom());
    assertEquals(englishAppPerson.getNickname(), frenchAppPerson.getSurnom());
    assertEquals(englishAppPerson.getAge(), frenchAppPerson.getAge());
}

Wie im Test gezeigt, akzeptiertDozerBeanMapper eine Liste benutzerdefinierter XML-Zuordnungsdateien und entscheidet, wann diese zur Laufzeit verwendet werden sollen.

Angenommen, wir beginnen jetzt, diese Datenobjekte zwischen unserer englischen und der französischen App hin und her zu verschieben. Wir müssen kein weiteres Mapping in der XML-Datei erstellen,Dozer is smart enough to map the objects both ways with only one mapping configuration:

@Test
public void givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_
  whenMapsBidirectionally_thenCorrect() {
    configureMapper("dozer_mapping.xml");
    Person englishAppPerson = new Person("Dwayne Johnson", "The Rock", 44);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(),englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}

In diesem Beispieltest wird daher eine weitere Funktion von Dozer verwendet - die Tatsache, dassthe Dozer mapping engine is bi-directional. Wenn wir also das Zielobjekt dem Quellobjekt zuordnen möchten, müssen wir der XML-Datei keine weitere Klassenzuordnung hinzufügen.

Wir können auch eine benutzerdefinierte Zuordnungsdatei von außerhalb des Klassenpfads laden. Verwenden Sie bei Bedarf das Präfix "file:" im Ressourcennamen.

In einer Windows-Umgebung (wie im folgenden Test) verwenden wir natürlich die Windows-spezifische Dateisyntax.

Auf einer Linux-Box können wir die Datei unter/home speichern und dann:

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

Und unter Mac OS:

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

Wenn Sie die Komponententests vongithub project ausführen (was Sie sollten), können Sie die Zuordnungsdatei an den entsprechenden Speicherort kopieren und die Eingabe für die MethodeconfigureMapper ändern.

Die Mapping-Datei finden Sie im Ordner test / resources des GitHub-Projekts:

@Test
public void givenMappingFileOutsideClasspath_whenMaps_thenCorrect() {
    configureMapper("file:E:\\dozer_mapping.xml");
    Person englishAppPerson = new Person("Marshall Bruce Mathers III","Eminem", 43);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(),englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}

6. Platzhalter und weitere XML-Anpassung

Erstellen wir eine zweite benutzerdefinierte Zuordnungsdatei mit dem Namendozer_mapping2.xml:



    
        com.example.dozer.Personne
        com.example.dozer.Person
        
            nom
            name
        
        
            surnom
            nickname
        
    

Beachten Sie, dass wir dem<mapping></mapping>-Element ein Attributwildcard hinzugefügt haben, das zuvor nicht vorhanden war.

Standardmäßig istwildcardtrue. Hiermit wird der Dozer-Engine mitgeteilt, dass alle Felder im Quellobjekt den entsprechenden Zielfeldern zugeordnet werden sollen.

Wenn wir es auffalse, setzen, weisen wir Dozer an, nur Felder zuzuordnen, die wir explizit im XML angegeben haben.

In der obigen Konfiguration möchten wir also nur zwei Felder zuordnen, wobeiage weggelassen werden:

@Test
public void givenSrcAndDest_whenMapsOnlySpecifiedFields_thenCorrect() {
    configureMapper("dozer_mapping2.xml");
    Person englishAppPerson = new Person("Shawn Corey Carter","Jay Z", 46);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(),englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), 0);
}

Wie wir in der letzten Behauptung sehen können, blieb das Feldagedes Ziels0.

7. Benutzerdefinierte Zuordnung über Anmerkungen

Für einfache Zuordnungsfälle und Fälle, in denen wir auch Schreibzugriff auf die Datenobjekte haben, die wir zuordnen möchten, müssen wir möglicherweise keine XML-Zuordnung verwenden.

Das Mappen von anders benannten Feldern über Annotationen ist sehr einfach und wir müssen viel weniger Code schreiben als beim XML-Mapping, können uns aber nur in einfachen Fällen helfen.

Replizieren wir unsere Datenobjekte inPerson2.java undPersonne2.java, ohne die Felder zu ändern.

Um dies zu implementieren, müssen wir nur die Annotation von @mapper(“destinationFieldName”)zu den Methoden vongetterim Quellobjekt hinzufügen. Wie so:

@Mapping("name")
public String getNom() {
    return nom;
}

@Mapping("nickname")
public String getSurnom() {
    return surnom;
}

Dieses Mal behandeln wirPersonne2 als Quelle, aber es spielt aufgrund derbi-directional nature der Dozer Engine keine Rolle.

Nachdem der gesamte XML-Code entfernt wurde, ist unser Testcode kürzer:

@Test
public void givenAnnotatedSrcFields_whenMapsToRightDestField_thenCorrect() {
    Person2 englishAppPerson = new Person2("Jean-Claude Van Damme", "JCVD", 55);
    Personne2 frenchAppPerson = mapper.map(englishAppPerson, Personne2.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(), englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}

Wir können auch die Bidirektionalität testen:

@Test
public void givenAnnotatedSrcFields_whenMapsToRightDestFieldBidirectionally_
  thenCorrect() {
    Personne2 frenchAppPerson = new Personne2("Jason Statham", "transporter", 49);
    Person2 englishAppPerson = mapper.map(frenchAppPerson, Person2.class);

    assertEquals(englishAppPerson.getName(), frenchAppPerson.getNom());
    assertEquals(englishAppPerson.getNickname(), frenchAppPerson.getSurnom());
    assertEquals(englishAppPerson.getAge(), frenchAppPerson.getAge());
}

8. Benutzerdefinierte API-Zuordnung

In unseren vorherigen Beispielen, in denen wir die Zuordnung von Datenobjekten aus einer französischen Anwendung aufheben, haben wir XML und Anmerkungen verwendet, um die Zuordnung anzupassen.

Eine andere in Dozer verfügbare Alternative, ähnlich der Annotation-Zuordnung, ist die API-Zuordnung. Sie ähneln sich, weil wir die XML-Konfiguration eliminieren und ausschließlich Java-Code verwenden.

In diesem Fall verwenden wir die KlasseBeanMappingBuilder, die in unserem einfachsten Fall wie folgt definiert ist:

BeanMappingBuilder builder = new BeanMappingBuilder() {
    @Override
    protected void configure() {
        mapping(Person.class, Personne.class)
          .fields("name", "nom")
            .fields("nickname", "surnom");
    }
};

Wie wir sehen können, haben wir eine abstrakte Methode,configure(), die wir überschreiben müssen, um unsere Konfigurationen zu definieren. Dann definieren wir genau wie unsere<mapping></mapping>-Tags in XML so vieleTypeMappingBuilders, wie wir benötigen.

Diese Builder teilen Dozer mit, welche Quell- und Zielfelder zugeordnet werden sollen. Wir übergeben dann dieBeanMappingBuilder anDozerBeanMapper wie die XML-Zuordnungsdatei, nur mit einer anderen API:

@Test
public void givenApiMapper_whenMaps_thenCorrect() {
    mapper.addMapping(builder);

    Personne frenchAppPerson = new Personne("Sylvester Stallone", "Rambo", 70);
    Person englishAppPerson = mapper.map(frenchAppPerson, Person.class);

    assertEquals(englishAppPerson.getName(), frenchAppPerson.getNom());
    assertEquals(englishAppPerson.getNickname(), frenchAppPerson.getSurnom());
    assertEquals(englishAppPerson.getAge(), frenchAppPerson.getAge());
}

Die Mapping-API ist auch bidirektional:

@Test
public void givenApiMapper_whenMapsBidirectionally_thenCorrect() {
    mapper.addMapping(builder);

    Person englishAppPerson = new Person("Sylvester Stallone", "Rambo", 70);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(), englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), englishAppPerson.getAge());
}

Oder wir können festlegen, dass nur explizit angegebene Felder mit dieser Builder-Konfiguration zugeordnet werden:

BeanMappingBuilder builderMinusAge = new BeanMappingBuilder() {
    @Override
    protected void configure() {
        mapping(Person.class, Personne.class)
          .fields("name", "nom")
            .fields("nickname", "surnom")
              .exclude("age");
    }
};

und unserage==0 Test ist zurück:

@Test
public void givenApiMapper_whenMapsOnlySpecifiedFields_thenCorrect() {
    mapper.addMapping(builderMinusAge);
    Person englishAppPerson = new Person("Sylvester Stallone", "Rambo", 70);
    Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);

    assertEquals(frenchAppPerson.getNom(), englishAppPerson.getName());
    assertEquals(frenchAppPerson.getSurnom(), englishAppPerson.getNickname());
    assertEquals(frenchAppPerson.getAge(), 0);
}

9. Benutzerdefinierte Konverter

Ein anderes Szenario, dem wir beim Mapping begegnen können, ist das, in dem wirperform custom mapping between two objects möchten.

Wir haben uns Szenarien angesehen, in denen sich die Namen von Quell- und Zielfeldern wie im französischenPersonne-Objekt unterscheiden. Dieser Abschnitt löst ein anderes Problem.

Was ist, wenn ein Datenobjekt, das wir nicht bereitstellen, ein Datums- und Zeitfeld wielong oder Unix-Zeit wie folgt darstellt:

1182882159000

Unser eigenes äquivalentes Datenobjekt repräsentiert jedoch dasselbe Datums- und Zeitfeld und denselben Wert in diesem ISO-Format, z. B.String:

2007-06-26T21:22:39Z

Der Standardkonverter würde den langen Wert einfach einemString wie folgt zuordnen:

"1182882159000"

Dies würde definitiv unsere App stören. Wie lösen wir das? Wir lösen es durchadding a configuration block in der Mapping-XML-Datei undspecifying our own converter.

Replizieren wir zunächst das DTOPersonder Remote-Anwendung mitname,, dann Geburtsdatum und -zeit,dtob Feld:

public class Personne3 {
    private String name;
    private long dtob;

    public Personne3(String name, long dtob) {
        super();
        this.name = name;
        this.dtob = dtob;
    }

    // standard getters and setters
}

und hier ist unsere eigene:

public class Person3 {
    private String name;
    private String dtob;

    public Person3(String name, String dtob) {
        super();
        this.name = name;
        this.dtob = dtob;
    }

    // standard getters and setters
}

Beachten Sie den Typunterschied vondtob in den Quell- und Ziel-DTOs.

Erstellen wir auch unsere eigenenCustomConverter, die im Mapping-XML an Dozer übergeben werden sollen:

public class MyCustomConvertor implements CustomConverter {
    @Override
    public Object convert(Object dest, Object source, Class arg2, Class arg3) {
        if (source == null)
            return null;

        if (source instanceof Personne3) {
            Personne3 person = (Personne3) source;
            Date date = new Date(person.getDtob());
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            String isoDate = format.format(date);
            return new Person3(person.getName(), isoDate);

        } else if (source instanceof Person3) {
            Person3 person = (Person3) source;
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            Date date = format.parse(person.getDtob());
            long timestamp = date.getTime();
            return new Personne3(person.getName(), timestamp);
        }
    }
}

Wir müssen nur die Methode vonconvert()überschreiben und dann alles zurückgeben, was wir zurückgeben möchten. Wir sind mit den Quell- und Zielobjekten und deren Klassentypen beschäftigt.

Beachten Sie, wie wir für die Bidirektionalität gesorgt haben, indem wir davon ausgegangen sind, dass die Quelle eine der beiden Klassen sein kann, die wir zuordnen.

Wir werden aus Gründen der Übersichtlichkeit eine neue Zuordnungsdatei erstellen,dozer_custom_convertor.xml:



    
        
            
                com.example.dozer.Personne3
                com.example.dozer.Person3
            
        
    

Dies ist die normale Zuordnungsdatei, die wir in den vorhergehenden Abschnitten gesehen haben. Wir haben nur einen<configuration></configuration>-Block hinzugefügt, in dem wir so viele benutzerdefinierte Konverter definieren können, wie wir mit ihren jeweiligen Quell- und Zieldatenklassen benötigen.

Testen wir den Code unseres neuenCustomConverter:

@Test
public void givenSrcAndDestWithDifferentFieldTypes_whenAbleToCustomConvert_
  thenCorrect() {

    configureMapper("dozer_custom_convertor.xml");
    String dateTime = "2007-06-26T21:22:39Z";
    long timestamp = new Long("1182882159000");
    Person3 person = new Person3("Rich", dateTime);
    Personne3 person0 = mapper.map(person, Personne3.class);

    assertEquals(timestamp, person0.getDtob());
}

Wir können auch testen, ob es bidirektional ist:

@Test
public void givenSrcAndDestWithDifferentFieldTypes_
  whenAbleToCustomConvertBidirectionally_thenCorrect() {
    configureMapper("dozer_custom_convertor.xml");
    String dateTime = "2007-06-26T21:22:39Z";
    long timestamp = new Long("1182882159000");
    Personne3 person = new Personne3("Rich", timestamp);
    Person3 person0 = mapper.map(person, Person3.class);

    assertEquals(dateTime, person0.getDtob());
}

10. Fazit

In diesem Tutorial haben wirintroduced most of the basics of the Dozer Mapping library und wie man es in unseren Anwendungen verwendet.

Die vollständige Implementierung all dieser Beispiele und Codefragmente finden Sie im Dozergithub project.