wordpress 多站点管理,工厂办公室装修设计,网站建设做好了怎样链接域名,沈阳男科医院好排行在Java开发工作中#xff0c;有很多时候我们需要将不同的两个对象实例进行属性复制#xff0c;从而基于源对象的属性信息进行后续操作#xff0c;而不改变源对象的属性信息。这两个对象实例有可能是同一个类的两个实例#xff0c;也可能是不同类的两个实例#xff0c;但是…在Java开发工作中有很多时候我们需要将不同的两个对象实例进行属性复制从而基于源对象的属性信息进行后续操作而不改变源对象的属性信息。这两个对象实例有可能是同一个类的两个实例也可能是不同类的两个实例但是他们的属相名称相同。例如DO、DTO、VO、DAO等这些实体的意义请查看DDD中分层架构。本文主要介绍几种对象拷贝的方法 1. 对象拷贝 对象拷贝分为深拷贝和浅拷贝。根据使用场景进行不同选择。在Java中数据类型分为值类型基本数据类型和引用类型值类型包括int、double、byte、boolean、char等简单数据类型引用类型包括类、接口、数组等复杂类型。
深度拷贝和浅度拷贝的主要区别在于是否支持引用类型的属性拷贝本文将探讨目前使用较多的几种对象拷贝的方案以及其是否支持深拷贝和性能对比。 2. BeanUtils 2.1 apache的BeanUtils方案
使用org.apache.commons.beanutils.BeanUtils进行对象深入复制时候主要通过向BeanUtils框架注入新的类型转换器因为默认情况下BeanUtils对复杂对象的复制是引用例如
public static void beanUtilsTest() throws Exception {// 注册转化器BeanUtilsBean.getInstance().getConvertUtils().register(new ArbitrationConvert(), ArbitrationDO.class);Wrapper wrapper new Wrapper();wrapper.setName(copy);wrapper.setNameDesc(copy complex object!);wrapper.setArbitration(newArbitrationDO());Wrapper dest new Wrapper();// 对象复制BeanUtils.copyProperties(dest, wrapper);// 属性验证wrapper.getArbitration().setBizId(1);System.out.println(wrapper.getArbitration() dest.getArbitration());System.out.println(wrapper.getArbitration().getBizId().equals(dest.getArbitration().getBizId()));
}public class ArbitrationConvert implements Converter {Overridepublic T T convert(ClassT type, Object value) {if (ArbitrationDO.class.equals(type)) {try {return type.cast(BeanUtils.cloneBean(value));} catch (Exception e) {e.printStackTrace();}}return null;}
}可以发现使用org.apache.commons.beanutils.BeanUtils复制引用时主和源的引用为同一个即改变了主的引用属性会影响到源的引用所以这是一种浅拷贝。
需要注意的是apache的BeanUtils中以下类型如果为空会报错org.apache.commons.beanutils.ConversionException: No value specified for *
/*** Register the converters for other types.* /p* This method registers the following converters:* ul* liClass.class - {link ClassConverter}* lijava.util.Date.class - {link DateConverter}* lijava.util.Calendar.class - {link CalendarConverter}* liFile.class - {link FileConverter}* lijava.sql.Date.class - {link SqlDateConverter}* lijava.sql.Time.class - {link SqlTimeConverter}* lijava.sql.Timestamp.class - {link SqlTimestampConverter}* liURL.class - {link URLConverter}* /ul* param throwException codetrue if the converters should* throw an exception when a conversion error occurs, otherwise code* codefalse if a default value should be used.*/private void registerOther(boolean throwException) {register(Class.class, throwException ? new ClassConverter() : new ClassConverter(null));register(java.util.Date.class, throwException ? new DateConverter() : new DateConverter(null));register(Calendar.class, throwException ? new CalendarConverter() : new CalendarConverter(null));register(File.class, throwException ? new FileConverter() : new FileConverter(null));register(java.sql.Date.class, throwException ? new SqlDateConverter() : new SqlDateConverter(null));register(java.sql.Time.class, throwException ? new SqlTimeConverter() : new SqlTimeConverter(null));register(Timestamp.class, throwException ? new SqlTimestampConverter() : new SqlTimestampConverter(null));register(URL.class, throwException ? new URLConverter() : new URLConverter(null));}当遇到这种问题是可以手动将类型转换器注册进去比如data类型public class BeanUtilEx extends BeanUtils { private static Map cache new HashMap();
private static Log logger LogFactory.getFactory().getInstance(BeanUtilEx.class); private BeanUtilEx() {
} static {
// 注册sql.date的转换器即允许BeanUtils.copyProperties时的源目标的sql类型的值允许为空
ConvertUtils.register(new org.apache.commons.beanutils.converters.SqlDateConverter(null), java.sql.Date.class);
ConvertUtils.register(new org.apache.commons.beanutils.converters.SqlDateConverter(null), java.util.Date.class);
ConvertUtils.register(new org.apache.commons.beanutils.converters.SqlTimestampConverter(null), java.sql.Timestamp.class);
// 注册util.date的转换器即允许BeanUtils.copyProperties时的源目标的util类型的值允许为空
} public static void copyProperties(Object target, Object source)
throws InvocationTargetException, IllegalAccessException {
// 支持对日期copy
org.apache.commons.beanutils.BeanUtils.copyProperties(target, source); } 2.2 apache的PropertyUtils方案 PropertyUtils的copyProperties()方法几乎与BeanUtils.copyProperties()相同主要的区别在于后者提供类型转换功能即发现两个JavaBean的同名属性为不同类型时在支持的数据类型范围内进行转换PropertyUtils不支持这个功能所以说BeanUtils使用更普遍一点犯错的风险更低一点。而且它仍然属于浅拷贝。
Apache提供了 SerializationUtils.clone(T)T对象需要实现 Serializable 接口他属于深克隆。 2.3 spring的BeanUtils方案
Spring中的BeanUtils其中实现的方式很简单就是对两个对象中相同名字的属性进行简单get/set仅检查属性的可访问性。
public static void copyProperties(Object source, Object target) throws BeansException {copyProperties(source, target, (Class)null, (String[])null);}public static void copyProperties(Object source, Object target, Class? editable) throws BeansException {copyProperties(source, target, editable, (String[])null);}public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {copyProperties(source, target, (Class)null, ignoreProperties);}private static void copyProperties(Object source, Object target, Class? editable, String... ignoreProperties) throws BeansException {Assert.notNull(source, Source must not be null);Assert.notNull(target, Target must not be null);Class actualEditable target.getClass();if(editable ! null) {if(!editable.isInstance(target)) {throw new IllegalArgumentException(Target class [ target.getClass().getName() ] not assignable to Editable class [ editable.getName() ]);}actualEditable editable;}PropertyDescriptor[] targetPds getPropertyDescriptors(actualEditable);List ignoreList ignoreProperties ! null?Arrays.asList(ignoreProperties):null;PropertyDescriptor[] var7 targetPds;int var8 targetPds.length;for(int var9 0; var9 var8; var9) {PropertyDescriptor targetPd var7[var9];Method writeMethod targetPd.getWriteMethod();if(writeMethod ! null (ignoreList null || !ignoreList.contains(targetPd.getName()))) {PropertyDescriptor sourcePd getPropertyDescriptor(source.getClass(), targetPd.getName());if(sourcePd ! null) {Method readMethod sourcePd.getReadMethod();if(readMethod ! null ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {try {if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {readMethod.setAccessible(true);}Object ex readMethod.invoke(source, new Object[0]);if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {writeMethod.setAccessible(true);}writeMethod.invoke(target, new Object[]{ex});} catch (Throwable var15) {throw new FatalBeanException(Could not copy property \ targetPd.getName() \ from source to target, var15);}}}}}}
可以看到, 成员变量赋值是基于目标对象的成员列表, 并且会跳过ignore的以及在源对象中不存在的, 所以这个方法是安全的, 不会因为两个对象之间的结构差异导致错误, 但是必须保证同名的两个成员变量类型相同. 3. dozer
Dozerhttp://dozer.sourceforge.net/能够实现深拷贝。Dozer是基于反射来实现对象拷贝反射调用set/get 或者是直接对成员变量赋值 。 该方式通过invoke执行赋值实现时一般会采用beanutil, Javassist等开源库。
简单引用网上的例子大多都是基于xml的配置具体请查看其它Blog package com.maven.demo;import java.util.HashMap;
import java.util.Map;import org.dozer.DozerBeanMapper;
import org.junit.Test;import static org.junit.Assert.assertEquals;public class Demo{/*** map-bean*/Testpublic void testDozer1() {MapString,Object map new HashMap();map.put(id, 10000L);map.put(name, 小兵);map.put(description, 帅气逼人);DozerBeanMapper mapper new DozerBeanMapper();ProductVO product mapper.map(map, ProductVO.class);assertEquals(小兵,product.getName());assertEquals(帅气逼人,product.getDescription());assertEquals(Long.valueOf(10000), product.getId());}/*** VO -- Entity (不同的实体之间不同的属性字段进行复制*/Testpublic void testDozer2(){ProductVO product new ProductVO();product.setId(10001L);product.setName(xiaobing);product.setDescription(酷毙了);DozerBeanMapper mapper new DozerBeanMapper();ProductEntity productEntity mapper.map(product, ProductEntity.class);assertEquals(xiaobing,productEntity.getProductName());}}4. MapStrcut MapStrcut属于编译期的对象复制方案它能够动态生成set/get代码的class文件 在运行时直接调用该class文件。该方式实际上扔会存在set/get代码只是不需要自己写了。 Mapper(componentModel spring)
public interface MonitorAppGroupIdcDTOMapper {MonitorAppGroupIdcDTOMapper MAPPER Mappers.getMapper(MonitorAppGroupIdcDTOMapper.class);void mapping(MonitorAppGroupIdcDTO source, MappingTarget MonitorAppGroupIdcDTO dest);
}5. 自定义Pojoconvert
public J copyPojo( P src, J des) throws NoSuchMethodException,SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {if(src null || desnull){return null;}String name null ;String sufix null;Class? cls des.getClass() ;Method[] methods cls.getMethods();for(Method m: methods){name m.getName();if(name!null name.startsWith(set) m.getParameterTypes().length1){sufix name.substring(3);m.getParameterTypes() ;Method getM cls.getMethod(getsufix);m.invoke(des, getM.invoke(src));}}return des ;
} 没有那么多验证不是很安全但是性能不错。 6. BeanCopier
Testpublic void test_convert_entity_to_model_performance_use_beancopier(){ListShopCouponEntity entityList ...long start System.currentTimeMillis();BeanCopier b BeanCopier.create(ShopCouponEntity.class, ShopCouponModel.class, false);ListShopCouponModel modelList new ArrayList();for (ShopCouponEntity src : entityList) {ShopCouponModel dest new ShopCouponModel();b.copy(src, dest, null);modelList.add(dest);}System.out.printf(BeanCopier took time: %d(ms)%n,System.currentTimeMillis() - start);}
可以通过缓存BeanCopier的实例来提高性能。 BeanCopier b getFromCache(sourceClass,targetClass); //从缓存中取long start System.currentTimeMillis();ListShopCouponModel modelList new ArrayList();for (ShopCouponEntity src : entityList) {ShopCouponModel dest new ShopCouponModel();b.copy(src, dest, null);modelList.add(dest);}7. fastjson和GSON 使用fastjson和GSON主要是通过对象json序列化和反序列化来完成对象复制这里只是提供一种不一样的对象拷贝的思路例子略。 8. 性能
对两种BeanUtils、Gson以及自定义Pojoconvert测试了性能
NewNovelMode des null ;
NewNovelMode ori buildModel();
Gson gson new Gson();
int count 100000;
//org.springframework.beans.BeanUtils.copyProperties
long s System.currentTimeMillis();
for(int i0;icount;i){des new NewNovelMode();org.springframework.beans.BeanUtils.copyProperties(ori, des);
}
System.out.println(springframework BeanUtils cost:(System.currentTimeMillis() - s));
// System.out.println(new Gson().toJson(des));//org.apache.commons.beanutils.BeanUtils
s System.currentTimeMillis();
for(int i0;icount;i){des new NewNovelMode();org.apache.commons.beanutils.BeanUtils.copyProperties(des, ori);
}
System.out.println(apache BeanUtils cost:(System.currentTimeMillis() - s));
// System.out.println(new Gson().toJson(des));//gson转换
s System.currentTimeMillis();
for(int i0;icount;i){des gson.fromJson(gson.toJson(ori), NewNovelMode.class);
}
System.out.println(gson cost:(System.currentTimeMillis() - s));
// System.out.println(new Gson().toJson(des));//Pojo转换类
s System.currentTimeMillis();
PojoUtilsNewNovelMode, NewNovelMode pojoUtils new PojoUtilsNewNovelMode, NewNovelMode();
for(int i0;icount;i){des new NewNovelMode();pojoUtils.copyPojo(ori,des);
}
System.out.println(Pojoconvert cost:(System.currentTimeMillis() - s));
// System.out.println(new Gson().toJson(des));
结果就不贴出来了在这里总结一下
Spring的BeanUtils比较稳定不会因为量大了耗时明显增加但其实基准耗时比较长apache的BeanUtils稳定性与效率都不行不可取Gson,因为做两个gson转换所以正常项目中可能耗时会更少一些PojoUtils稳定不如spring但是总耗时优势明显原因是它只是根据项目的需求实现的简单的转换模板这个代码在其它的几个工具类均有。
而在网上的其他Blog中参见Reference对Apache的BeanUtils、PropertyUtils和CGLIB的BeanCopier作了性能测试。
测试结果
性能对比 BeanCopier BeanUtils. 其中BeanCopier的性能高出另外两个100数量级。
综上推荐使用
1. BeanUtils简单易用
2. BeanCopier加入缓存后和手工set的性能接近
3. Dozer深拷贝
4. fastjson特定场景下使用 转自https://my.oschina.net/hosee/blog/1483965