Dozer

ここでは、Dozerライブラリを使用した簡単な実装サンプルと、処理性能測定結果(気になったので実施してみました)を示します。


1.Java環境

動作確認時に使用したJava環境を以下に示す。

■Java

1.8.0_172

■Dozer

dozer-5.5.1.jar
dozer-spring-5.5.1.jar

■その他必要ライブラリ

commons-beanutils-1.9.2.jar
commons-lang3-3.3.2.jar
commons-logging-1.1.1.jar
javax.inject-1.jar
logback-classic-1.1.11.jar
logback-core-1.1.11.jar
slf4j-api-1.7.25.jar
spring-aop-4.3.14.RELEASE.jar
spring-beans-4.3.14.RELEASE.jar
spring-context-4.3.14.RELEASE.jar
spring-core-4.3.14.RELEASE.jar
spring-expression-4.3.14.RELEASE.jar

2.サンプル実装概要

下図に示す構成で、DozerBeanMapperFactoryBeanライブラリを使用して、モデルクラスModelAからModelBへのBeanマッピング(フィールド値コピー)を行います。

Dozerライブラリのインスタンス化はSpringにて行う。

下表にモデルクラスのフィールド定義を示します。


3.サンプル実装

DozerDemoMain.java

/**
 * Dozerデモクラス実行用メインクラス
 */
package xxx.dozer;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DozerDemoMain {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        DozerDemo dozerDemo = ctx.getBean(DozerDemo.class);
        dozerDemo.performDemo();
    }
}


DozerDemo.java

/**
 * Dozerデモクラス
 */
package xxx.dozer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.dozer.Mapper;
import org.springframework.stereotype.Component;

@Component
public class DozerDemo {
    @Inject
    Mapper beanMapper;

    public void performDemo() {
        //複製元モデルクラス
        ModelA modelA = createTestObject();
        //複製先モデルクラス
        ModelB modelB;

        long start;
        long end;

        //アクセサによるコピー
        for (int i = 0; i < 10; i++) {
            start = System.nanoTime();
            modelB = convertModel(modelA);
            end = System.nanoTime();
            System.out.print("no dozer process time[ns]:" + (end - start));
            System.out.print("\titem1:" + modelB.getItem1());
            System.out.print("\titem2:" + modelB.getItem2());
            System.out.print("\titem3[2]:" + modelB.getItem3().get(2));
            System.out.print("\titem4[2]:" + modelB.getItem4().get(2));
            System.out.print("\titem5.item3[2]:" + modelB.getItem5().getItem3().get(2));
            System.out.print("\titem5.item4[2]:" + modelB.getItem5().getItem4().get(2));
            System.out.print("\titemB1:" + modelB.getItemB1());
            System.out.println("\titemB10:" + modelB.getItemB10());
        }
        //Dozerによるコピー
        for (int i = 0; i < 10; i++) {
            start = System.nanoTime();
            modelB = convertModelDozer(modelA);
            end = System.nanoTime();
            System.out.print("with dozer processs time[ns]:" + (end - start));
            System.out.print("\titem1:" + modelB.getItem1());
            System.out.print("\titem2:" + modelB.getItem2());
            System.out.print("\titem3[2]:" + modelB.getItem3().get(2));
            System.out.print("\titem4[2]:" + modelB.getItem4().get(2));
            System.out.print("\titem5.item3[2]:" + modelB.getItem5().getItem3().get(2));
            System.out.print("\titem5.item4[2]:" + modelB.getItem5().getItem4().get(2));
            System.out.print("\titemB1:" + modelB.getItemB1());
            System.out.println("\titemB10:" + modelB.getItemB10());
        }
    }

    /**
     * ModelA->ModelBプロパティ値コピー(Dozer使用無し)
     * @param model
     * @return
     */
    private ModelB convertModel(ModelA model) {
        ModelB outModel = new ModelB();
        outModel.setItem1(model.getItem1());
        outModel.setItem2(String.valueOf(model.getItem2()));
        outModel.setItem3(model.getItem3());
        outModel.setItem4(model.getItem4());
        outModel.setItem5(model.getItem5());
        outModel.setItem6(model.getItem6());
        outModel.setItem7(model.getItem7());
        outModel.setItem8(model.getItem8());
        outModel.setItem9(model.getItem9());
        outModel.setItem10(model.getItem10());
        outModel.setItemB1(model.getItemA1());
        outModel.setItemB2(model.getItemA2());
        outModel.setItemB3(model.getItemA3());
        return outModel;
    }

    /**
     * ModelA->ModelBプロパティ値コピー(Dozer利用)
     * @param model
     * @return
     */
    private ModelB convertModelDozer(ModelA model) {
        ModelB outModel = beanMapper.map(model, ModelB.class);
        return outModel;
    }

    /**
     * テストデータ
     * @return
     */
    private ModelA createTestObject() {
        ModelA modelA = new ModelA();
        modelA.setItem1("1");
        modelA.setItem2(2);

        List<String> list = new ArrayList<String>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        modelA.setItem3(list);

        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(0, "AA");
        map.put(1, "BB");
        map.put(2, "CC");
        modelA.setItem4(map);

        ModelC modelC = new ModelC();
        modelC.setItem1("101");
        modelC.setItem2(102);

        List<String> listC = new ArrayList<String>();
        listC.add("aaa");
        listC.add("bbb");
        listC.add("ccc");
        modelC.setItem3(listC);

        Map<Integer, String> mapC = new HashMap<Integer, String>();
        mapC.put(0, "AAA");
        mapC.put(1, "BBB");
        mapC.put(2, "CCC");
        modelC.setItem4(mapC);

        modelA.setItem5(modelC);
        modelA.setItem6("6");
        modelA.setItem7("7");
        modelA.setItem8("8");
        modelA.setItem9("9");
        modelA.setItem10("10");
        modelA.setItemA1("A1");
        return modelA;
    }
}

ModelA.java    ※ModelB、ModelCクラスは記載省略(2.サンプル実装概要の表を参照)

package xxx.dozer;

import java.util.List;
import java.util.Map;

public class ModelA {

    private String item1;
    private int item2;
    private List<String> item3;
    private Map<Integer,String> item4;
    private ModelC item5;
    private String item6;
    private String item7;
    private String item8;
    private String item9;
    private String item10;

    private String itemA1;
    private String itemA2;
    private String itemA3;
    private String itemA4;
    private String itemA5;
    private String itemA6;
    private String itemA7;
    private String itemA8;
    private String itemA9;
    private String itemA10;

    public String getItem1() {
        return item1;
    }
    public void setItem1(String item1) {
        this.item1 = item1;
    }
//※以降のアクセサメソッド記載省略
}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       ">

    <context:component-scan base-package="xxx.dozer" />

    <bean class="org.dozer.spring.DozerBeanMapperFactoryBean">
        <property name="mappingFiles" value="classpath*:/dozer/*-mapping.xml" />
    </bean>
</beans>

demo-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>xxx.dozer.ModelA</class-a>
      <class-b>xxx.dozer.ModelB</class-b>
      <field>
        <a>itemA1</a>
        <b>itemB1</b>
      </field>
      <field>
        <a>itemA2</a>
        <b>itemB2</b>
      </field>
      <field>
        <a>itemA3</a>
        <b>itemB3</b>
      </field>
    </mapping>

</mappings>

4.処理性能測定結果

ModelA⇒ModelBのコピー処理時間を下記に示す。(Dozer無しのコピーとDozerによるコピーをそれぞれプロセス内で10回実行した結果)

no dozer process time[ns]:477433
no dozer process time[ns]:12800
no dozer process time[ns]:18559
no dozer process time[ns]:11520
no dozer process time[ns]:12800
no dozer process time[ns]:23040
no dozer process time[ns]:17919
no dozer process time[ns]:23040
no dozer process time[ns]:18560
no dozer process time[ns]:21120
with dozer processs time[ns]:474844850
with dozer processs time[ns]:8005002
with dozer processs time[ns]:4458174
with dozer processs time[ns]:3982662
with dozer processs time[ns]:7100056
with dozer processs time[ns]:5984552
with dozer processs time[ns]:3338831
with dozer processs time[ns]:12294219
with dozer processs time[ns]:3781704
with dozer processs time[ns]:4045381

最大値は繰り返し処理時の初回にそれぞれ477μsec、475msec。

(また、初回は色んな内部処理のオーバーヘッドを含んでいるとも推測する)

Dozer使用時の475msecはアプリケーション処理性能に影響しそうな数値だが、実行環境に大きく依存するので、ここでは気にしない。


相対比較(1回目以外)をしてみるとDozer使用時は使用しない場合に比べて186~625倍の処理時間がかかっている。

これら結果からは、Dozer使用時は実装ソースコードは劇的にシンプルにできる反面、その副作用としての処理性能影響は認識して実装をすべきと考える。


例えば、フィールドコピー数が2、3項目ならDozerを使用しないほうが

・何をコピーしているかが直観でわかる

・使わないフィールドの無駄なコピーが発生しない

・不用意にコピーしたフィールド値による想定外動作を招かない

というようなメリットはあると認識する。


しかし、getter/seterによるフィールドコピー実装が業務ロジック上ずらっと並ぶ実装状態は保守観点において個人的にはげんなりしてしまう。


Dozer使用有無はプログラムやプログラマ単位で考えるのではなく、

・システムやサブシステム全体でのDozerを意識したモデルクラス全体設計

・Dozer使用有無の基準ルール化

・フィールド名命名ルール化

などの開発準備があってのDozer活用が理想と考える。(処理性能劣化と相殺もしくはそれ以上のメリットは得られる)