Web アプリなんかを作っていると、よく DB 側のサービス層から取得したオブジェクトを Web 層側のフォームBean に変換して使用したり、またその逆を行ったりという事をよくやります。 こんな感じですね↓
SimpleDateFormate sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // サービスクラスから id をキーにして UserModel を取得 UserModel model = userService.getUser(id); // フォームBeanを作成して、プロパティを1つ1つコピー UserForm form = new UserForm(); form.setName(model.getName()); form.setLoginDate(sdf.format(model.getLoginDate())); ...
UserForm も UserModel も同じ名前のプロパティ(name, loginDate)を持っているのですが、 UserForm クラスは Web層で使用するフォームBean の為、 全てのプロパティが String で構成 (※) されています。一方、 UserModel クラスはドメインモデルであり、 そのまま DB とも対応したりしている為、それぞれのプロパティが適切な型に設定されています。
この例のようにプロパティが2つぐらいなら大した手間ではありませんが、 大きなオブジェクトになるとプロパティは10個にも20個にもなります。 それを上記の例のように1つ1つコピーしていると、 プログラムを書く上でミスが生じる可能性も高くなりますし、 可読性も下がります。何よりめんどくさいです…
そんな時に便利なのが、 Jakarta Commons の BeanUtils コンポーネントです。BeanUtils は Struts プロジェクトの副産物として生まれたコンポーネントですが、 このライブラリを使うと、下記のようにコードがすっきりします。
// サービスクラスから id をキーにして UserModel を取得 UserModel model = userService.getUser(id); // フォームBeanを作成して、BeanUtils でプロパティをコピー UserForm form = new UserForm(); BeanUtils.copyProperties(form, model); ...
本当にコピーされているのかどうか、 下記のようなプログラム[ サンプルソース ]を作成して試してみました。
-+-+-+-+-+-+-+-+-+-+-
UserModel.java
-+-+-+-+-+-+-+-+-+-+-
package jp.javable.sample.beanutils;
import java.io.Serializable;
import java.util.Date;
public class UserModel implements Serializable {
private String name;
private Date loginDate;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getLoginDate() {
return loginDate;
}
public void setLoginDate(Date loginDate) {
this.loginDate = loginDate;
}
}
-+-+-+-+-+-+-+-+-+-+-
UserForm.java
-+-+-+-+-+-+-+-+-+-+-
package jp.javable.sample.beanutils;
import java.io.Serializable;
public class UserForm implements Serializable {
private String name;
private String loginDate;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLoginDate() {
return loginDate;
}
public void setLoginDate(String loginDate) {
this.loginDate = loginDate;
}
public String toString() {
return this.getClass().getName() + "@"
+ Long.toHexString(System.identityHashCode(this))
+ "[name=" + this.getName()
+ ",loginDate=" + this.getLoginDate() + "]";
}
}
-+-+-+-+-+-+-+-+-+-+-
Main.java
-+-+-+-+-+-+-+-+-+-+-
package jp.javable.sample.beanutils;
import org.apache.commons.beanutils.BeanUtils;
public class Main {
public static void main(String[] args) throws Exception {
UserModel model = new UserModel();
model.setName("溝口");
model.setLoginDate(new java.util.Date());
UserForm form = new UserForm();
// 間違えやすいですが、このメソッドは右から左(第2引数から第1引数)にコピーします
BeanUtils.copyProperties(form, model);
System.out.println(form);
}
}
実行してみます。
D:\eclipse_workspace\test> java -classpath bin;lib\commons-beanutils.jar;
lib\commons-collections.jar;
lib\commons-logging.jar jp.javable.sample.beanutils.Main
(↑実際にはすべて1行です)
jp.javable.sample.beanutils.UserForm@1027b4d[name=溝口,
loginDate=Thu May 19 23:02:18 JST 2005]
ちゃんとコピーされました! しかし、最初の例では loginDate は yyyy/MM/dd HH:mm:ss 形式の文字列にしていたのですが、 このケースでは java.util.Date#toString() の結果がそのまま格納されています。 このままでは使えません。 これを何とかする方法は、 BeanUtils で独自のコンバーターを使用する に書きます。
ちなみに上記の実行例でも分かりますが、 BeanUtils は Commons Collection と Commons Logging を内部で使用しているので、 実行時には上記 2つのライブラリが必要 (※)となります。