Top > Blog > Programming

BeanUtils で独自のコンバーターを使用する

BeanUtils で JavaBeans のプロパティを一気にコピーする で作成したサンプルでは、 java.util.Date を String にコピーした時に “yyyy/MM/dd HH:mm:ss” というような任意のフォーマットへの変換は出来ませんでした。 これは BeanUtils のデフォルトの振る舞いが java.util.Date#toString() の値を使用して String へ変換するようになっている為です。 まずその仕組みを少し調べ、独自の変換を行うにはどうすれば良いかを試してみます。

BeanUtils が型の違うプロパティを変換する仕組み

BeanUtils ではコピー元とコピー先のオブジェクトを比較して、 同じ名前のプロパティがあれば、 その型が違っても自動で変換してくれます。 例えば下図において、 コピー元の UserModel#getLoginDate() が java.util.Date を返すのに対し、 コピー先の UserForm#setLoginDate(String) は String を設定するメソッドになっています。

このように型が違うものを自動変換する仕組みとして、 BeanUtils は Converter インターフェースを用意しています。 デフォルトで表1のコンバーターが登録されているため、 プリミティブ型や基本クラスに関しては自動変換できるようになっています。

表1:デフォルトで登録されているコンバータ
コピー先プロパティの型 導入Ver. 対応する(※1)コンバーター(実際には1行) デフォルトの変換内容(BeanUtils 1.7)
コピー元の値が…
null 同じ型 その他
boolean 1.3 Boolean
Converter
false そのまま <元の値>.toString() が “yes”, “y”, “true”, “on”, “1” ならばtrue, “no”, “n”, “false”, “off”, “0” ならば false (大文字小文字は区別しない)
Boolean 同上 Boolean.FALSE 同上
char 1.3 Character
Converter
半角スペース そのまま <元の値>.toString().charAt(0)
例外発生時は null の場合と同じ。
Charcter 同上 new Charcter(‘ ‘) 同上
String 1.3 String
Converter
null <元の値>.toString()
byte 1.3 Byte
Converter
0 そのまま 元の値が Number のサブクラス(※2) ならば、((Number)<元の値>).byteValue()
それ以外は new Byte(<元の値>.toString()) で例外発生時は null の場合と同じ。
Byte 同上 new Byte((byte)0) 同上
short 1.3 Short
Converter
0 そのまま 元の値が Number のサブクラス(※2) ならば、((Number)<元の値>).shortValue()
それ以外は new Short(<元の値>.toString()) で例外発生時は null の場合と同じ。
Short 同上 new Short(0) 同上
int 1.3 Integer
Converter
0 そのまま 元の値が Number のサブクラス(※2) ならば、((Number)<元の値>).intValue()
それ以外は new Integer(<元の値>.toString()) で例外発生時は null の場合と同じ。
Integer 同上 new Integer(0) 同上
long 1.3 Long
Converter
0 そのまま 元の値が Number のサブクラス(※2) ならば、((Number)<元の値>).longValue()
それ以外は new Long(<元の値>.toString()) で例外発生時は null の場合と同じ。
Long 同上 new Long(0) 同上
float 1.3 Float
Converter
0.0 そのまま 元の値が Number のサブクラス(※2) ならば、((Number)<元の値>).floatValue()
それ以外は new Float(<元の値>.toString()) で例外発生時は null の場合と同じ。
Float 同上 new Float((float)0.0) 同上
double 1.3 Double
Converter
0.0 そのまま 元の値が Number のサブクラス(※2) ならば、((Number)<元の値>).doubleValue()
それ以外は new Double(<元の値>.toString()) で例外発生時は null の場合と同じ。
Double 同上 new Double((double)0.0) 同上
boolean[] 1.4 BooleanArray
Converter
new boolean[0] そのまま boolean と同じルールで変換された boolean 配列
char[] 1.4 CharacterArray
Converter
new char[0] そのまま char と同じルールで変換された char 配列。例外発生時は null の場合と同じ。
String[] 1.4 StringArray
Converter
new String[0] そのまま 元の値が int 配列ならば、Integer.toString(<元の値の各要素>) して作成した配列。
それ以外は <元の値の各要素>.toString()) して作成した配列で、例外発生時は null の場合と同じ。
byte[] 1.4 ByteArray
Converter
new byte[0] そのまま Byte.parseByte(<元の値の各要素>.toString()) して作成した配列。 例外発生時は null の場合と同じ。
short[] 1.4 ShortArray
Converter
new short[0] そのまま Short.parseShort(<元の値の各要素>.toString()) して作成した配列。 例外発生時は null の場合と同じ。
int[] 1.4 IntegerArray
Converter
new int[0] そのまま Integer.parseInt(<元の値の各要素>.toString()) して作成した配列。 例外発生時は null の場合と同じ。
long[] 1.4 LongArray
Converter
new long[0] そのまま Long.parseLong(<元の値の各要素>.toString()) して作成した配列。 例外発生時は null の場合と同じ。
float[] 1.4 FloatArray
Converter
new float[0] そのまま Float.parseFloat(<元の値の各要素>.toString()) して作成した配列。 例外発生時は null の場合と同じ。
double[] 1.4 DoubleArray
Converter
new double[0] そのまま Double.parseDouble(<元の値の各要素>.toString()) して作成した配列。 例外発生時は null の場合と同じ。
BigDecimal 1.3 BigDecimal
Converter
例外発生(※3) そのまま new BigDecimal(<元の値>.toString())
BigInteger 1.3 BigInteger
Converter
例外発生(※3) そのまま new BigInteger(<元の値>.toString())
java.sql.
Date
1.3 SqlDate
Converter
例外発生(※3) そのまま java.sql.Date#valueOf(<元の値>.toString())。例外発生時は null の場合と同じ。
java.sql.
Time
1.3 SqlTime
Converter
例外発生(※3) そのまま java.sql.Time#valueOf(<元の値>.toString())。例外発生時は null の場合と同じ。
java.sql.
Timestamp
1.3 SqlTimestamp
Converter
例外発生(※3) そのまま java.sql.Timestamp#valueOf(<元の値>.toString())。例外発生時は null の場合と同じ。
Class 1.4 Class
Converter
例外発生(※3) そのまま 現在のスレッドのコンテキストクラスローダ、または ClassConverter クラスのクラスローダから ClassLoader#loadClass(<元の値>.toString()) した値。例外発生時は null の場合と同じ。
java.io.
File
1.7 File
Converter
例外発生(※3) そのまま new File(<元の値>.toString())
java.net.
URL
1.7 URL
Converter
例外発生(※3) そのまま new URL(<元の値>.toString())。例外発生時は null の場合と同じ。
※1コンバータクラスのパッケージは全て org.apache.commons.beanutils.converters
※2 java.lang.Number のサブクラスは BigDecimal,BigInteger,Byte,Double,Float,Integer,Long,Short
※3 投げられる例外は org.apache.commons.beanutils.ConversionException

BeanUtils でコンバーターが使用される手順

では、 java.util.Date 型 → String 型 と変換する際、 BeanUtils#copyProperties(Object, Object) ではどのような処理を行っているのでしょうか? 前述の loginDate というプロパティをコピーする際の処理を下記に示します。 (下の例では実際の処理内容を単純化しています。)

  1. UserModel(コピー元)#getLoginDate() の値を取得
  2. UserForm(コピー先)の loginDate という名前のプロパティに関する PropertyDescripter を取得
  3. 取得した PropertyDescripter で、コピー先プロパティの型(この場合は String)を取得
  4. 取得した型に対応するコンバーターを ConvertUtils から取得(この場合は StringConverter)
  5. Converter#convert(Class, Object) メソッドを使用して 1 で取得した値を変換
  6. 変換済みの値を UserForm(コピー先)の setter メソッドを使用して設定

つまり、 UserModel の loginDate は Date 型ですが、これを変換する際には、 UserForm の loginDate が String である為、 あらかじめ ConvertUtils に登録されている StringConverter が使用されています。 StringConverter の実装はというと…

public final class StringConverter implements Converter {

    public Object convert(Class type, Object value) {
        if (value == null) {
            return ((String) null);
        } else {
            return (value.toString());
        }
    }
}

…となっています。 第1引数の type には、この場合、常に String.class という値が渡されます。 第2引数の value が変換前の値で、 実際に UserModel#getLoginDate() で取得した java.util.Date 型の値が渡されます。 これで BeanUtils#copyProperties(Object, Object) を使用した際に java.util.Date#toString() の値がコピー先に設定された理由が分かりました。

独自のコンバーターを作成・登録

BeanUtils がコンバーターを使用している仕組みが分かったところで、 loginDate を “yyyy/MM/dd HH:mm:ss” 形式で変換できるように、 独自のコンバーターを作成してみます。

public final class CustomStringConverter implements Converter {

    public Object convert(Class type, Object value) {
        if (value == null) {
            return ((String) null);
        } else {
            if (value instanceof java.util.Date) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                return sdf.format((java.util.Date)value);
            }
            return (value.toString());
        }
    }
}
このコンバータを使用するには、 ConvertUtils#register(Converter, Class) メソッドを使用します。
-+-+-+-+-+-+-+-+-+-+-
Main.java
-+-+-+-+-+-+-+-+-+-+-
package jp.javable.sample.beanutils;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;

public class Main {

    public static void main(String[] args) throws Exception {
        
        ConvertUtils.register(new CustomStringConverter(), String.class);
        
        UserModel model = new UserModel();
        model.setName("溝口");
        model.setLoginDate(new java.util.Date());

        UserForm form = new UserForm();
        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@145d068[name=溝口,
            loginDate=2005/05/20 23:24:12]

ちゃんとフォーマットされていました(T^T)。[ サンプルソース ]

コメントの投稿