はじめまして。酒井です。
現在、Javaでの開発プロジェクトに参加しています。
Java自体はスマホアプリ開発でも使っていたので馴染みはあるのですが、今回はWebサービスということで、色々と初めてで勝手が分からないことだらけで、チームにフォローしてもらいながら作業を進めています。
今回は、開発中に出会ったBeanマッパーを紹介します。
Beanマッピングとは、ヒトコトで言うと、BeanからBeanへのフィールドコピーです。
こちらの解説が簡潔で分かりやすかったです。
現プロジェクトの担当箇所にて、ある一連の処理で、中間のオブジェクトから出力に使うオブジェクトへ値をコピーする必要があり、「こんなの手で書くもんじゃないだろう」と思って、メンバーに聞いたりググったりしてみました。
MapStruct
まず試してみたのはMapStruct。
リフレクションではなくコード生成するタイプのもので、高速に動作するのがウリ。
ClassA
からClassB
へのマッピングを行う場合、以下のようなinterface
を定義します。
1 | @Mapper |
interface
名および変換メソッド名は自由です。INSTANCE
はマッピングを行う際に利用します。
マッピングする方のコードは以下のようになります。
1 | ClassA a = new ClassA(); |
双方同じ名前のフィールド/プロパティ(xxxx
というフィールドに対してsetXxxx()
とgetXxxx()
があるもの)でのコピーであれば、これだけで実現できます。AMapper
の実装を書く必要もありません。素晴らしい。
ところが。
残念ながらMapStructは採用しませんでした。何故かと言うと・・・僕が怠惰だったからです。
ClassA
が以下のようなフィールドを持っていて、
1 | public class ClassA { |
各フィールドに対するsetter
/getter
を書きたくなったのです。
Eclipseで自動生成してくれますが、そういうことじゃないんです。分かりますよね?(笑)
プロジェクトで既に利用されていたLombokを使うと、
1 | @Data |
アノテーション1つで解決です。こうでないと。
で、MapStructはLombokと相性が悪かったのです。LombokはJavaコンパイラに介入してコードを追加したりするのですが、MapStructがコードを生成するのはそれより前であるため、上記のクラス定義のまま、private
フィールドでsetter
がない、という状況なので、エラーになってしまいました。
MapStructを使うために手でsetter
/getter
を書くか、それを避けるためにMapStructを諦めるか。[※]
MapStruct諦めました。
Orika
MapStructを諦めて、Orikaを試すことにしました。
“Java Bean mapper”でググると、Dozerというヤツの方が先に出てくるのですが、色々なところで速度比較が行われていて、遅いらしいということが分かったので、そこで比較対象として書かれていたOrikaに決めました。
Orikaでは、ClassA
とClassB
の例のような双方同じ名前のフィールドでのコピーであれば、MapStructのように事前定義が必要なものはありません。
マッピングを行う側では、まずMapperFactory
のインスタンスを取得します。
1 | MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); |
上記は毎回行う必要はなく、シングルトンインスタンスとして保持しておきます。
プロジェクトではSpringを使っているので、DIで対応しました。
マッピングは以下のようにします。
1 | MapperFacade mapper = mapperFactory.getMapperFacade(); |
マッピング元と先が決まっている場合、さらに効率のよいBoundMapperFacade
を利用できます。
1 | BoundMapperFacade<ClassA, ClassB> mapper = mapperFactory.getMapperFacade(ClassA.class, ClassB.class); |
Javaでバリバリ開発してきた方には当たり前は話かも知れないのですが、長年関わってきた組み込み開発ではこれが必要な状況にならず、今まで出会わずに来たので、ちょっと感動。
以上です。
(2015-11-12追記)
※両方を使う例はあるのですが、プロジェクトを分ける必要があります。