当前位置: iiBetTer|太好了 > 技术创新, 神来之笔 > 文章正文

陷阱,看上去很美

Joe 发表于 2010-07-13 06:45 | 阅读 180 views

慎用BeanUtils.copyProperties(Object,Object)转换Bean(VO,PO

问题描述:

最近在帮忙ERefund DEBUG一些问题。在ERefund里出现这样一个问题,前台一个NumberField不输入任何值,但是在后台查询数据库的时候却是以0去作为查询条件来查询数据,这样查出的结果肯定跟用户期望的结果不一致。

起初,怀疑是DWR或者数据类型在作祟,在将UI上JavaScript的值传给后台时赋了默认值?但是DEBUG后发现,事实并非如此,DWR并没有强加默认值,所以可以排除DWR了。Façade层再没有任何针对该VO的操作了,所以Façade可以PASS了。而且检查了相应的Bean,所有属性都没有定义简单数据类型,比如long、int、double、float之类的,所以也排除了使用了简单数据类型的错误。

“杯具”往往由“洗具”开始,这话真不假。

接着,又大略浏览了一下整个VO在Service层传输之后,我似乎发现了有可能导致问题的关键点。那就是在Service层里,前台的VO和后台数据库操作的PO会有一个转换(背景:整个项目是MVC架构,Struts+Spring+iBatis,前台和后台各有各自的VO,无可厚非,而且这样做避免了前台的VO一捅到底,将PO直接暴露给客户,也避免了将来UI的变化导致的牵一动百的“杯具”),在做VOPO的转换时,由于前台的VO和后台的PO属性完全相同,所以只用一句代码就搞定了VOPO的转换,即:BeanUtils.copyProperties(PO,VO)或者BeanUtilsBean.getInstance().copyProperties(PO,VO),其实这两个最终的结果是一样的,BeanUtils的copyPreperties(PO,VO)方法就是调用的BeanUtilsBean的copyPreperties(PO,VO)。其实这是“洗具”,因为一个庞大的VOPO就用一句代码搞定了他俩的转换,一是省去了写大量的get/set方法减少了大量代码,二是代码更加易读,是可喜可贺的。但是“杯具”开始了,我看了下这两个类所在Jar(commons-beanutils.jar)的源代码,发现它会去根据这个属性的type在ConvertUtilsBean里lookup(Converter是一个接口)而在ConvertUtilsBean的构造方法里面有如下代码:

/* 151*/ defaultBoolean = Boolean.FALSE;

/* 179*/ defaultByte = new Byte((byte)0);

/* 207*/ defaultCharacter = new Character(‘ ‘);

/* 237*/ defaultDouble = new Double(0.0D);

/* 265*/ defaultFloat = new Float(0.0F);

/* 293*/ defaultInteger = new Integer(0);

/* 321*/ defaultLong = new Long(0L);

很显然,这就是“杯具”的发源地咯。它会自己将Boolean、Byte、Character、Double、Float、Integer、Long型初始化,并且赋予默认值。事实摆在眼前了,百口莫辩了,导致问题的根源就是这个BeanUtils.copyProperties(PO,VO)啦。

解决方案:

其实在commons-beanutils.jar里面也提供了另外一个用来作为Bean转换的工具类:PropertyUtils.copyProperties(PO,VO),这个方法就不会给Boolean、Byte、Character、Double、Float、Integer、Long这些类型赋默认值。

PropertyUtils.copyProperties方法来转换Bean固然省事,但对需要转换的两个Bean的要求也高,那就是VOPO里面需要转换的属性必须完全一致,如果出现不一致,将不会被转换或者会出现Exception,我试过一个属性数相同,但是最后一个属性不同名称,测试结果是,其他属性都转换了,最后一个属性不会转换。

BeanUtils.copyProperties方法和PropertyUtils.copyProperties方法的大概区别是:前者会给某些类型强加默认值,后者不会;前者会自动智能进行类型转换(在允许转换的类型中)只要属性名一样,后者不会,也正因为如此,后者在处理效率上会比前者快。而这两者都是基于反射的机制,所以相比get/set效率都会略有下降。

除了这两种转换Bean的简单办法(不用自己写get/set方法来转换)之外,还有一个DozerDozerBeanMapper可以做,而且功能很强大,比上述两种都强大。通过在XML文件里灵活的配置,可以达到不同对象间的拷贝,而且如果两个对象属性基本一致,那你只需要将属性名称不一致的匹配写在XML文件里面,其他一致的,你可以不写,但需要将wildcard配置为true,就是意味着对所有属性都进行转换。如果为false,则只对在XML文件中配置的属性进行转换。

测试目的:

测试在做VOPO转换时,是否会对NULLABLE的属性强加默认值。

测试用例:

package test;

import java.lang.reflect.InvocationTargetException;

import java.util.ArrayList;

import java.util.List;

import org.apache.commons.beanutils.BeanUtils;

import org.apache.commons.beanutils.BeanUtilsBean;

import org.apache.commons.beanutils.PropertyUtils;

import org.dozer.DozerBeanMapper;

/*

* Copyright(C) 2010.

* Application : strutsT

* Class Name : .TestBeanUtils

* Date Created: Jul 13, 2010

* Author : Joe Jiang

*/

/**

* @author Joe Jiang

*

*/

public class TestBeanUtils {

/**

* @param args

*/

public static void main(String[] args) {

// TODO Auto-generated method stub

try {

TestVO tvo = new TestVO();

System.out.println(tvo.toString());

ATestVO atvo = new ATestVO();

BeanUtils.copyProperties(atvo, tvo);

System.out.println(atvo.toString());

ATestVO btvo = new ATestVO();

BeanUtilsBean bu = BeanUtilsBean.getInstance();

bu.copyProperties(btvo, tvo);

System.out.println(btvo.toString());

ATestVO ctvo = new ATestVO();

PropertyUtils.copyProperties(ctvo, tvo);

System.out.println(ctvo.toString());

ATestVO dtvo = new ATestVO();

DozerBeanMapper mapper = new DozerBeanMapper();

List myMappingFiles = new ArrayList();

myMappingFiles.add("dozerBeanMapping.xml");

mapper.setMappingFiles(myMappingFiles);

dtvo = mapper.map(tvo, ATestVO.class);

System.out.println(dtvo.toString());

} catch (IllegalAccessException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (InvocationTargetException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (NoSuchMethodException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

TestVO属性:

private String tString;

private Long tLong;

private long tlong;

private Integer tInteger;

private int tint;

private Float tFloat;

private float tfloat;

private Double tDouble;

private double tdouble;

private boolean tboolean;

private Boolean tBoolean;

private byte tbyte;

private Byte tByte;

private short tshort;

private Short tShort;

private char tchar;

private Character tCharacter;

private Date tDate;

测试结果:

====new TestVO()====

TestVO[tdouble:0.0 tfloat:0.0 tint:0 tlong:0 tString:null tDouble:null tFloat:null tInteger:null tLong:null tboolean:false tBoolean:null tbyte:0 tByte:null tshort:0 tShort:null tchar: tCharacter:null tDate:null]

====BeanUtils.copyProperties====

ATestVO[tdouble:0.0 tfloat:0.0 tint:0 tlong:0 tString:null tDouble:0.0 tFloat:0.0 tInteger:0 tLong:0 tboolean:false tBoolean:false tbyte:0 tByte:0 tshort:0 tShort:0 tchar: tCharacter: tDate:null]

====BeanUtilsBean.getInstance().copyProperties====

ATestVO[tdouble:0.0 tfloat:0.0 tint:0 tlong:0 tString:null tDouble:0.0 tFloat:0.0 tInteger:0 tLong:0 tboolean:false tBoolean:false tbyte:0 tByte:0 tshort:0 tShort:0 tchar: tCharacter: tDate:null]

====PropertyUtils.copyProperties====

ATestVO[tdouble:0.0 tfloat:0.0 tint:0 tlong:0 tString:null tDouble:null tFloat:null tInteger:null tLong:null tboolean:false tBoolean:null tbyte:0 tByte:null tshort:0 tShort:null tchar: tCharacter:null tDate:null]

====dozer====

ATestVO[tdouble:0.0 tfloat:0.0 tint:0 tlong:0 tString:null tDouble:null tFloat:null tInteger:null tLong:null tboolean:false tBoolean:null tbyte:0 tByte:null tshort:0 tShort:null tchar: tCharacter:null tDate:null]

总结:个人推荐还是用Dozer来做前台VO和后台PO的转换,相比之下,dozer更能适合项目后期的各种Change Request。

喜欢 iiBetTer|太好了 的文章,那就通过 RSS Feed 功能订阅吧!

我要评论

* 必须

* 必须, 绝不会泄露


主机导购



返回首页 | 博主相册 | 关于我们 | 联系我们 | 广告合作 | 网站地图 | 生活百宝箱 | 网址导航 | 版权声明