对象复制在目前的业务系统中,往往需要两个对象进行属性复制。不可否认,一个一个地复制对象是最快最安全的方法,但是当对象的属性字段数量超过程序员的承受能力时,代码就会变得臃肿。使用一些方便的对象复制工具类会是一个不错的选择。有些实体(DTO、VO、DO或PO等)在模型数据转换工程中或多或少会被转换,往往具有相同的属性名。当数量较少时,我们可以直接使用set和get方法来赋值。但是如果很多地方都用到这样的转换,如果还是set操作的话势必会大大影响开发效率。关于实体转换,我们将一个实体映射到一个表(这个可以看成是DO)。业务中与第三方的数据交互,我们需要将实体数据传递给他们,但不一定一个DO中的所有属性都可以归约或由多个DO中的属性组成。这里我们引入DTO(在这个实体中,我们可以去掉一些隐私信息,比如:银行卡号,身份证,密码)。对于性别,我们使用1和2来代表男性和女性。页面不能直接显示1或2,需要显示men,women,或者prettyboys(男)和prettygirls(女)。这时候,我们可以把这样一个实体看成是VO。目前流行和公认的工具:Apache的两个版本:(反射机制)org.apache.commons.beanutils.PropertyUtils.copyProperties(Objectdest,Objectorig)原因:dateTimeConveter的converter不处理null值//特殊属性的限制targetObject:(Date,BigDecimal,etc.)publicclassBeanObject{//这里省略了getter和setter方法privateStringname;privatejava.util.Datedate;}publicclassBeanObjectTest{publicstaticvoidmain(Stringargs[])throwsThrowable{BeanObjectfrom=newBeanObject();BeanObjectto=newBeanObject();//from.setDate(newjava.util.Date());from.setName("TTTT");org.apache.commons.beanutils.BeanUtils.copyProperties(to,from);//如果去掉from.setDate,这里会出现转换器异常System.out.println(ToStringBuilder.reflectionToString(from));System.out.println(ToStringBuilder.reflectionToString(to));}}org.apache.commons.beanutils.BeanUtils.copyProperties(Objectdest,Objectorig)属性名相同,类型不匹配时的处理原因:这两个工具类不支持同名匹配但是不同的类型!!![封装类Long和原始数据类型long是可以接受的]publicclassSourceClass{//这里省略getter,setter方法privateLongnum;privateStringname;}publicclassTargetClass{//这里省略getter,setter方法privateLongnum;私有字符串名称;}公共类PropertyUtilsTest{publicstaticvoidmain(Stringargs[])throwsIllegalAccessException,InvocationTargetException,NoSuchMethodException{来自.setNum(1);from.setName("姓名");TargetClassto=newTargetClass();//抛出参数不匹配异常org.apache.commons.beanutils.PropertyUtils.copyProperties(to,from);org.springframework.beans.BeanUtils.copyProperties(从,到);//抛出参数不匹配异常System.out.println(ToStringBuilder.reflectionToString(from));System.out.println(ToStringBuilder.reflectionToString(to));}}Spring版本:(反射机制)org.springframework.beans.BeanUtils.copyProperties(Objectsource,Objecttarget,Classeditable,String[]ignoreProperties)cglib版本:(使用动态代理,效率高)cglib是比较java字节码操作底层框架net.sf.cglib.beans.BeanCopier.copy(ObjectparamObject1,ObjectparamObject2,ConverterparamConverter)工具运行原理简介反射型:(apache)使用静态类调用,最后转换virtualmachinePublicBeanUtilsBean(){this(newConvertUtilsBean(),newPropertyUtilsBean());}通过ConvertUtils可以全局注册ConvertUtilsBean。ConvertUtils.register(newDateConvert(),java.util.Date.class);PropertyUtilsBean的copyProperties方法实现了复制算法。动态bean:originstanceofDynaBean:Objectvalue=((DynaBean)orig).get(name);然后将值复制到动态bean类。Map类型:originstanceofMap:keyvalue一份一份复制其他普通类:从beanInfo中取出name【每个对象都有一个缓存的bean信息,包括属性字段等】,然后将sourceClass和targetClass一份一份复制。Cglib类型:BeanCopiercopier=BeanCopier.create(source.getClass(),target.getClass(),false);copier.copy(source,target,null);handlemismatchbetweenGetandsetmethodspublicclassBeanCopierTest{/***从这个用例可以看出,BeanCopier.create的target.class的每个get方法都必须有一个set方法形成*@paramargs*/publicstaticvoidmain(Stringargs[]){BeanCopiercopier=BeanCopier.create(UnSatifiedBeanCopierObject.class,SourceClass.class,false);copier=BeanCopier.create(SourceClass.class,UnSatifiedBeanCopierObject.class,false);//这里抛出异常创建}}classUnSatifiedBeanCopierObject{privateStringname;私人长号;publicStringgetName(){undefined返回名称;}publicvoidsetName(Stringname){undefinedthis.name=name;}publicLonggetNum(){undefinedreturnnum;}//publicvoidsetNum(Longnum){undefined//this.num=num;//}}创建对象过程:生成sourceClass->TargetClass复制代理类,放到jvm中,所以在创建代理类的时候耗时的时候,最好保证这个对象的单例模式。可以参考上一部分的优化方案。创建过程->源码见jdk:net.sf.cglib.beans.BeanCopier.Generator.generateClass(ClassVisitor)获取sourceClass的所有publicget方法-》PropertyDescriptor[]getters获取TargetClass的所有publicset方法-》PropertyDescriptor[]setter遍历setter的每个属性,根据setter的名字执行4和5生成sourceClass的所有setter方法-》PropertyDescriptorgetter【不符合javabean规范的类可能会出现空指针异常]PropertyDescriptor[]setters-》PropertyDescriptorsetter将setter和getter的名称和类型配对,为代理类生成一个复制方法。原理总结Copyattribute过程:调用生成的代理类,代理类的代码和手动操作的代码很相似,效率很高。上述方法中最快的是BeanCopier。默认只复制同名同类型的字段,日期为空时不复制。我觉得最好这样做,比如把对象A的值复制到B,我们复制一样,把一些需要我们个性化的字段分离出来,用get来赋值,这样程序会很easy显然,重点在不同的地方。
