S-JIS[2007-12-05] 変更履歴

commons BeanUtils

Apache Jakartaプロジェクトで作られた、JavaBeansを扱うユーティリティー。
Strutsが結構使っているようだ。


インストール

  1. commons beanutilsダウンロードページからアーカイブをダウンロードする。(commons-beanutils-1.7.0.zip)
  2. 適当な場所にアーカイブを展開する。
  3. 展開したディレクトリの「commons-beanutils-1.7.0」の直下にあるcommons-beanutils.jarを適当な場所にコピーする。

クラスパスが通っている場所にjarファイルを置くか、クラスパス(ビルドパス)にjarファイルの指定を追加する。

また、実行にはcommons-loggingが必要。
(見つからない場合、java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactoryという例外が発生する)


Eclipse3.2を使っている場合、バージョン1.6のBeanUtilsがプラグインに入っている。

C:\eclipse\plugins\org.eclipse.tomcat_4.1.130.v20060601\commons-beanutils.jar
                                                        commons-collections.jar


使用方法

JavaBeansを扱うに当たり、主にBeanUtilsとPropertyUtilsというクラスがある(BeanだけじゃなくてMapやDynaBeanにも対応している)。
PropertyUtilsは基礎的なユーティリティー。
BeanUtilsは高機能で、内部ではPropertyUtilsを使用している箇所もある。デバッグログへの出力も実装されている(だからloggingが必要になってしまう訳だが…)

メソッド 戻り値の型 概要 備考(相違点)
BeanUtils PropertyUtils
setProperty(bean, name, value) void void bean内のnameにvalueをセットする。 nameには、単純なプロパティー名の他に、「arr[0]」といった配列の要素指定や、「sub.value」といった別オブジェクトのプロパティーを指定することが出来る。→プロパティー名のルール
getProperty(bean, name) String Object bean内のnameの値を返す。 PropertyUtilsでは、元のbeanの型そのまま(プリミティブラッパークラス)で値を返す。
BeanUtilsでは、PropertyUtilsを利用して値を取得し、Converterを使ってStringに変換してそれを返す。
copyProperties(dst, src) void void src内の各値をdstへセットする。 コピー元とコピー先でプロパティーの型が異なる場合、BeanUtilsではConverterを使ってコピー先の型に変換するが、PropertyUtilsでは互換性の無い型だったら例外が発生する。
describe(bean) Map Map bean内の各値をMapに入れて返す。 引数のbeanがnullのとき、PropertyUtilsは例外を発生させるがBeanUtilsは空のマップを返す。
populate(bean, map) void - map内の各値をbeanに入れる。  
cloneBean(bean) Object - beanのクローン(複製)を作成する。 beanのクラスを取得してnewInstance()でインスタンス生成してcopyProperties()で値をコピーしているので、clone()を実装する必要が無い。

このユーティリティー内部では、java.beansパッケージのPropertyDescriptorクラス等を使っている。
リフレクションの塊のようなものなので処理は重そうだが、クラス毎に情報をキャッシュしているので、繰り返し使う場合は負荷が軽減される。


コンバーター

BeanUtils#getProperty()では、本来の型をStringに変換した値を返す。また、BeanUtils#copyProperties()も、型が異なる場合はコピー先の型に変換する。
これらの変換はConvertUtilsというクラスが保持しているConverterクラスを用いて行われる。Converterは変換先の型に応じた具象クラスが用意されて いてデフォルトで使われており、自分で作ることも出来る。

BeanUtils1.7では、BeanUtilsの各メソッド(static)は、ことごとくBeanUtilsBeanというオブジェクトの同名メソッド(非static)を呼び出すようになっている(委譲している)。
(BeanUtils1.6まではBeanUtilsBeanというクラスは無く、BeanUtils内に直接ロジックが書かれていた模様)
PropertyUtilsも同様に、PropertyUtilsBeanのメソッドを呼んでいるだけ。
ConvertUtilsもConvertUtilsBeanのメソッドを呼んでいる。

BeanUtilsBeanのコンストラクターには、BeanUtilsBean自身が使用するPropertyUtilsBeanとConvertUtilsBeanのインスタンスも必要となる。
BeanUtilsは1つだけBeanUtilsBeanをインスタンス化するが、その際にPropertyUtilsBeanとConvertUtilsBeanもインスタンス化している。
PropertyUtilsとConvertUtilsは、“BeanUtilsが使用するBeanUtilsBean”で保持しているPropertyUtilsBeanとConvertUtilsBeanを取得して使っている。

独自コンバーターの登録には、ConvertUtils#register()というメソッドを使う。これを呼び出すと、デフォルトのConvertUtilsBeanインスタンスにコンバーターを登録することになる。
しかしこの方法では、BeanUtilsを使っている他のライブラリー(Struts等)の変換にも影響を与えてしまって好ましくない。
そこで、独自変換を行いたい場合は、BeanUtilsBeanを自分で生成して使用し、BeanUtilsは使わないようにするのが良い。

文字列へ変換する独自コンバーターの例:

import org.apache.commons.beanutils.Converter;

/**
 * @see StringConverter
 */
public class SampleStringConverter implements Converter {

	public Object convert(Class type, Object value) {
		// System.out.println(type); //当オブジェクトをString用として登録するので、typeは必ずStringのはず
		if (value == null) {
			return (String) null;
		} else if (value instanceof Date) {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
			return sdf.format(value);
		} else {
			return value.toString();
		}
	}
}

コピー元(Date型)Beanサンプル:

public class DateBean {
	private Date date;

	public void setDate(Date dt) {
		date = dt;
	}

	public Date getDate() {
		return date;
	}
}

コピー先(String型)Beanサンプル:

public class StringBean {
	private String date;

	public void setDate(String dt) {
		date = dt;
	}

	public String getDate() {
		return date;
	}
}

コンバーターの登録・使用例:

	DateBean   src = new DateBean();
	StringBean dst = new StringBean();

	// srcに現在日時をセット
	src.setDate(new Date());


//●BeanUtilsを使ってdstへコピー
	BeanUtils.copyProperties(dst, src);
	System.out.println(dst.getDate());


//●独自BeanUtilsBeanを作ってデフォルト状態でdstへコピー
	BeanUtilsBean bu = new BeanUtilsBean(
				new ConvertUtilsBean(), 	//独自コンバーターを登録する為に新しいインスタンスを生成
				BeanUtilsBean.getInstance().getPropertyUtils() //デフォルトのPropertyUtilsBeanを共用
			);
	bu.copyProperties(dst, src);
	System.out.println(dst.getDate());


//●独自BeanUtilsBeanに独自コンバーターをセットしてdstへコピー
	bu.getConvertUtils().register(
				new SampleStringConverter(), //独自コンバーターを
				String.class		  //String型への変換として登録
			);
	bu.copyProperties(dst, src);
	System.out.println(dst.getDate());


//●大元のBeanUtilsには影響してないことを確認
	BeanUtils.copyProperties(dst, src);
	System.out.println(dst.getDate());

実行結果:

Wed Dec 05 23:05:14 JST 2007	←BeanUtilsによるコピー
Wed Dec 05 23:05:14 JST 2007	←自分で生成したBeanUtilsBeanの初期状態によるコピー
2007/12/05 23:05:14		←独自コンバーターを使用したコピー
Wed Dec 05 23:05:14 JST 2007	←BeanUtilsによるコピー(独自コンバーターの影響を受けていない)

参考: Reo MIZOGUCHIさんのwww.Javable.Jp - Notes:BeanUtils で独自コンバーターを使用する場合の落とし穴
Converterの登録時にsynchronized(ConvertUtils.class)を使 う方法を紹介しているけど、StrutsがConvertUtils.classで排他しているのでない限り、同期オブジェクトはFastHashMap辺りにしないと意味ないんじゃ…?
ついでに言うと、コンバーターをデフォルトに戻すConvertUtils#deregister()は全コンバーターオブジェクトを作り直すので、けっこう重そう…。


Javaへ戻る / 技術メモへ戻る
メールの送信先:ひしだま