1. 概要

Dozer は、 JavaBeanからJavaBeanへのマッパーであり、属性ごとに1つのオブジェクトから別のオブジェクトにデータを再帰的にコピーします。

ライブラリは、Java Beanの属性名間のマッピングをサポートするだけでなく、タイプ間が異なる場合は自動的に変換します。

ほとんどの変換シナリオはそのままでサポートされますが、DozerではXMLを介してカスタム変換を指定することもできます。

2. 簡単な例

最初の例では、ソースデータオブジェクトと宛先データオブジェクトがすべて同じ共通の属性名を共有していると仮定します。

これは、Dozerで実行できる最も基本的なマッピングです。

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
}

次に、宛先ファイル Dest.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
}

Dozerは内部でリフレクションを使用するため、デフォルトまたはゼロ引数コンストラクターを含める必要があります。

また、パフォーマンスを向上させるために、マッパーをグローバルにして、テスト全体で使用する単一のオブジェクトを作成しましょう。

DozerBeanMapper mapper;

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

次に、最初のテストを実行して、 Source オブジェクトを作成するときに、それをDestオブジェクトに直接マップできることを確認しましょう。

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

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

ご覧のとおり、Dozerマッピング後、結果はSourceオブジェクトと同じフィールド名を持つすべてのフィールドの値を含むDestオブジェクトの新しいインスタンスになります。

または、 mapper Dest クラスを渡す代わりに、 Dest オブジェクトを作成し、mapperにその参照を渡すこともできます。

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

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

3. Mavenのセットアップ

Dozerがどのように機能するかについての基本的な理解ができたので、次の依存関係をpom.xmlに追加しましょう。

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.5.1</version>
</dependency>

最新バージョンはこちらで入手できます。

4. データ変換の例

すでに知っているように、Dozerは、両方のクラスで同じ名前の属性を見つける限り、既存のオブジェクトを別のオブジェクトにマップできます。

ただし、常にそうであるとは限りません。 したがって、マップされた属性のいずれかが異なるデータ型である場合、Dozerマッピングエンジンは自動的にデータ型変換を実行します。

この新しい概念の実際を見てみましょう。

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
}

そして、宛先クラス:

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
}

属性名は同じですが、それらのデータ型は異なります

ソースクラスでは、idStringであり、pointsdoubleですが、宛先クラスでは id[ X136X]とポイントは両方とも整数です。

Dozerが変換を正しく処理する方法を見てみましょう。

@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);
}

“ 320”15.2 Stringdoubleをソースオブジェクトに渡し、結果は 320[ X133X]と15、は両方とも宛先オブジェクトのintegerです。

5. XMLによる基本的なカスタムマッピング

これまで見てきたすべての例で、ソースデータオブジェクトと宛先データオブジェクトの両方が同じフィールド名を持っているため、私たちの側で簡単にマッピングできます。

ただし、実際のアプリケーションでは、マッピングする2つのデータオブジェクトに、共通のプロパティ名を共有するフィールドがない場合が数え切れないほどあります。

これを解決するために、DozerはXMLカスタムマッピング構成を作成するオプションを提供します。

このXMLファイルでは、Dozerマッピングエンジンがどのソース属性をどの宛先属性にマップするかを決定するために使用するクラスマッピングエントリを定義できます。

例を見てみましょう。フランスのプログラマーによって作成されたアプリケーションから、オブジェクトに名前を付ける英語スタイルへのデータオブジェクトの非整列化を試してみましょう。

name nickname ageフィールドを持つPersonオブジェクトがあります。

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
}

アンマーシャリングするオブジェクトの名前はPersonneで、フィールドは nom surnom ageです。

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
}

これらのオブジェクトは実際には同じ目的を達成しますが、言語の壁があります。 その障壁を支援するために、Dozerを使用して、フランス語のPersonneオブジェクトをPersonオブジェクトにマップできます。

Dozerがこれを行うのに役立つカスタムマッピングファイルを作成するだけで済みます。これをdozer_mapping.xmlと呼びます。

<?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>com.baeldung.dozer.Personne</class-a>
        <class-b>com.baeldung.dozer.Person</class-b>
        <field>
            <a>nom</a>
            <b>name</b>
        </field>
        <field>
            <a>surnom</a>
            <b>nickname</b>
        </field>
    </mapping>
</mappings>

これは、私たちが持つことができるカスタムXMLマッピングファイルの最も簡単な例です。

今のところ、私たちが持っていることに気付くのに十分です子を持つルート要素として 、これらの子供をできるだけ多く入れることができますカスタムマッピングを必要とするクラスペアの発生率があるため。

タグ内でソースクラスと宛先クラスを指定する方法にも注意してください。 この後に、カスタムマッピングが必要な送信元と宛先のフィールドペアごとに が続きます。

最後に、カスタムマッピングファイルにフィールドageが含まれていないことに注意してください。 フランス語で年齢を表す言葉はまだ年齢であり、これはブルドーザーのもう1つの重要な特徴につながります。

同じ名前のプロパティをマッピングXMLファイルで指定する必要はありません。 Dozerは、同じプロパティ名を持つすべてのフィールドをソースオブジェクトから宛先オブジェクトに自動的にマップします。

次に、カスタムXMLファイルをsrcフォルダーのすぐ下のクラスパスに配置します。 ただし、クラスパスのどこに配置しても、Dozerはクラスパス全体を検索して指定されたファイルを探します。

mapperにマッピングファイルを追加するヘルパーメソッドを作成しましょう。

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

コードをテストしてみましょう。

@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());
}

テストに示されているように、 DozerBeanMapper は、カスタムXMLマッピングファイルのリストを受け入れ、実行時にそれぞれをいつ使用するかを決定します。

英語アプリとフランス語アプリの間でこれらのデータオブジェクトの非マーシャリングを開始するとします。 XMLファイルに別のマッピングを作成する必要はありません。Dozerは、1つのマッピング構成だけでオブジェクトを双方向にマッピングするのに十分スマートです。

@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());
}

したがって、このテスト例では、このDozerの別の機能を使用しています。 Dozerマッピングエンジンは双方向であるため、宛先オブジェクトをソースオブジェクトにマッピングする場合は、追加する必要はありません。 XMLファイルへの別のクラスマッピング。

クラスパスの外部からカスタムマッピングファイルをロードすることもできます。必要に応じて、リソース名に「 file:」プレフィックスを使用します。

Windows環境(以下のテストなど)では、もちろんWindows固有のファイル構文を使用します。

Linuxボックスでは、ファイルを /homeの下に保存してから次のようにします。

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

そしてMacOSの場合:

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

githubプロジェクトから単体テストを実行している場合(必要です)、マッピングファイルを適切な場所にコピーして、configureMapperメソッドの入力を変更できます。

マッピングファイルは、GitHubプロジェクトのtest/resourcesフォルダーにあります。

@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. ワイルドカードとさらなるXMLのカスタマイズ

dozer_mapping2.xmlという2番目のカスタムマッピングファイルを作成しましょう。

<?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 wildcard="false">
        <class-a>com.baeldung.dozer.Personne</class-a>
        <class-b>com.baeldung.dozer.Person</class-b>
        <field>
            <a>nom</a>
            <b>name</b>
        </field>
        <field>
            <a>surnom</a>
            <b>nickname</b>
        </field>
    </mapping>
</mappings>

以前は存在しなかった要素に属性wildcardを追加したことに注意してください。

デフォルトでは、wildcardtrueです。 これは、ソースオブジェクトのすべてのフィールドを適切な宛先フィールドにマップすることをDozerエンジンに通知します。

false、に設定すると、XMLで明示的に指定したフィールドのみをマップするようにDozerに指示します。

したがって、上記の構成では、 age を除いて、2つのフィールドのみをマップする必要があります。

@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);
}

最後のアサーションでわかるように、宛先ageフィールドは0のままでした。

7. 注釈によるカスタムマッピング

単純なマッピングの場合、およびマップするデータオブジェクトへの書き込みアクセス権もある場合は、XMLマッピングを使用する必要がない場合があります。

アノテーションを介した異なる名前のフィールドのマッピングは非常に単純であり、XMLマッピングよりもはるかに少ないコードを記述する必要がありますが、単純な場合にのみ役立ちます。

フィールドをまったく変更せずに、データオブジェクトをPerson2。javaおよびPersonne2。javaに複製してみましょう。

これを実装するには、ソースオブジェクトのgetterメソッドに@mapper( “destinationFieldName”)アノテーションを追加するだけです。 そのようです:

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

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

今回はPersonne2をソースとして扱っていますが、ドーザーエンジンの双方向性のため、問題ではありません。

XML関連のコードがすべて削除されたので、テストコードは短くなります。

@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());
}

双方向性をテストすることもできます。

@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. カスタムAPIマッピング

フランスのアプリケーションからデータオブジェクトをアンマーシャリングする前の例では、XMLとアノテーションを使用してマッピングをカスタマイズしました。

アノテーションマッピングと同様に、Dozerで利用できるもう1つの代替手段は、APIマッピングです。 XML構成を排除し、Javaコードを厳密に使用するため、これらは類似しています。

この場合、 BeanMappingBuilder クラスを使用します。これは、次のような最も単純なケースで定義されています。

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

ご覧のとおり、抽象メソッド configure()があり、これをオーバーライドして構成を定義する必要があります。 次に、XMLの タグと同様に、必要な数のTypeMappingBuilderを定義します。

これらのビルダーは、マッピングしているソースから宛先のフィールドをDozerに通知します。 次に、BeanMappingBuilderDozerBeanMapperに、XMLマッピングファイルと同じように、異なる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());
}

マッピングAPIも双方向です。

@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());
}

または、このビルダー構成で明示的に指定されたフィールドのみをマップすることを選択できます。

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

そして、 age ==0テストが戻ってきました。

@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. カスタムコンバーター

マッピングで直面する可能性のある別のシナリオは、2つのオブジェクト間でカスタムマッピングを実行したい場合です。

フランス語のPersonneオブジェクトのように、送信元と宛先のフィールド名が異なるシナリオを見てきました。 このセクションでは、別の問題を解決します。

マーシャリングを解除するデータオブジェクトが、longやUnix時間などの日付と時刻のフィールドを表す場合はどうなりますか。

1182882159000

ただし、同等のデータオブジェクトは、 String:などのこのISO形式で同じ日付と時刻のフィールドと値を表します。

2007-06-26T21:22:39Z

デフォルトのコンバーターは、次のようにlong値をStringにマップするだけです。

"1182882159000"

これは間違いなく私たちのアプリをバグします。 では、これをどのように解決しますか? マッピングXMLファイルに構成ブロックを追加し、独自のコンバーターを指定することで解決します。

まず、リモートアプリケーションの Person DTOをname、、生年月日、dtobフィールドで複製しましょう。

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
}

そしてここに私たち自身があります:

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
}

ソースDTOと宛先DTOのdtobのタイプの違いに注意してください。

また、マッピングXMLでDozerに渡す独自のCustomConverterを作成しましょう。

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);
        }
    }
}

convert()メソッドをオーバーライドして、戻りたいものを返すだけです。 ソースオブジェクトと宛先オブジェクト、およびそれらのクラスタイプを利用できます。

ソースがマッピングしている2つのクラスのいずれかであると想定して、双方向性をどのように処理したかに注目してください。

わかりやすくするために、新しいマッピングファイルdozer_custom_convertor.xmlを作成します。

<?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">
    <configuration>
        <custom-converters>
            <converter type="com.baeldung.dozer.MyCustomConvertor">
                <class-a>com.baeldung.dozer.Personne3</class-a>
                <class-b>com.baeldung.dozer.Person3</class-b>
            </converter>
        </custom-converters>
    </configuration>
</mappings>

これは前のセクションで見た法線マッピングファイルです。 ブロックのみを追加しました。このブロック内に、それぞれのソースデータと宛先データで必要な数のカスタムコンバーターを定義できます。クラス。

新しいCustomConverterコードをテストしてみましょう。

@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());
}

また、双方向であることを確認するためにテストすることもできます。

@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. 結論

このチュートリアルでは、がDozerマッピングライブラリの基本のほとんどと、それをアプリケーションで使用する方法を紹介しました。

これらすべての例とコードスニペットの完全な実装は、Dozergithubプロジェクトにあります。