CommonsとSpringのBeanUtils.copyProperties

Commons BeanUtils に Bean のプロパティをコピーする BeanUtils.copyProperties() というなかなか便利なメソッドがありますが、同名のメソッドが Spring にもあります。 (当然ながらパッケージ名は異なります)
ちなみに Seasar2 にも BeanUtil.copyProperties() というものがあります。

ここで、Commons のものと Spring のもので引数の順序が逆になっていたりと紛らわしい違いがあるので簡単にまとめてみました。

分類 第一引数 第二引数 例外 MapからBeanへのコピー
Commons コピー先 コピー元 検査例外あり
Spring コピー元 コピー先 実行時例外のみ ×

パッケージ名や throws 定義されている例外は下記の通りです。

  • Commons 版
    • パッケージ: org.apache.commons.beanutils
    • 例外: IllegalAccessException(検査例外), InvocationTargetException(検査例外)
  • Spring 版
    • パッケージ: org.springframework.beans
    • 例外: BeansException(実行時例外)

また、Commons 版では Map から Bean へのプロパティコピーを実施できますが、Spring 版ではできない模様です。

そして、Commons 版では BigDecimal へ null 値をコピーするとデフォルトで下記のような例外が発生します。

BigDecimal へ null 値をコピーした場合の例外 (Commons 版)
org.apache.commons.beanutils.ConversionException: No value specified for 'BigDecimal'

これを防止するには ConvertUtils.register() を使って BigDecimalConverter を登録しておきます。

BigDecimal へ null 値をコピーするための対策 (Commons 版)
ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class)

動作確認

下記のようなサンプルスクリプトを使って挙動の違いを確認してみました。

copyproperties_sample.groovy
@Grab('commons-beanutils:commons-beanutils:1.8.3')
@Grab('org.springframework:spring-beans:3.2.4.RELEASE')
import org.apache.commons.beanutils.ConvertUtils
import org.apache.commons.beanutils.converters.BigDecimalConverter

import groovy.transform.*

@TupleConstructor
@ToString(includeNames=true)
class Data {
    String name
    BigDecimal value
}

// Commons 版 BeanUtils.copyProperties
def copyPropsCommons = { data ->
    println '----- Commons -----'

    try {
        def trg = new Data()
        org.apache.commons.beanutils.BeanUtils.copyProperties(trg, data)
        println trg
    } catch (e) {
        println e
    }
}
// Commons 版 BeanUtils.copyProperties with converter
def copyPropsCommons2 = { data ->
    println '----- Commons with converter -----'

    def trg = new Data()

    // null を BigDecimal のプロパティへコピーするための設定
    ConvertUtils.register(new BigDecimalConverter(null), BigDecimal)

    org.apache.commons.beanutils.BeanUtils.copyProperties(trg, data)

    // BigDecimalConverter の解除
    ConvertUtils.deregister()

    println trg
}
// Spring 版 BeanUtils.copyProperties
def copyPropsSpring = { data ->
    println '----- Spring -----'

    def trg = new Data()
    org.springframework.beans.BeanUtils.copyProperties(data, trg)
    println trg
}

def procList = [copyPropsCommons, copyPropsCommons2, copyPropsSpring]

def d1 = new Data('sample1', 100)

procList.each { it(d1) }

println ''

def d2 = new Data('sample2')

procList.each { it(d2) }

println ''

def d3 = [
    name: 'sample3',
    value: 10
]

// Commons 版は Map からのコピーが可能
copyPropsCommons(d3)
// Spring 版は Map からのコピーが不可
copyPropsSpring(d3)

実行結果は下記のようになりました。

実行結果
> groovy copyproperties_sample.groovy
----- Commons -----
Data(name:sample1, value:100)
----- Commons with converter -----
Data(name:sample1, value:100)
----- Spring -----
Data(name:sample1, value:100)

----- Commons -----
org.apache.commons.beanutils.ConversionException: No value specified for 'BigDecimal'
----- Commons with converter -----
Data(name:sample2, value:null)
----- Spring -----
Data(name:sample2, value:null)

----- Commons -----
Data(name:sample3, value:10)
----- Spring -----
Data(name:null, value:null)

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20131013/