当前位置: 首页 > 后端技术 > Java

Java对象复制原理及最佳实践解析

时间:2023-04-01 22:39:01 Java

作者:宁海翔1前言对象复制是我们开发过程中不可避免的过程。它存在于Po、Dto、Do、Vo各个表现层的数据转换中。它也存在于序列化和反序列化等系统交互中。Java对象拷贝分为深拷贝和浅拷贝。目前常用的属性拷贝工具,包括Apache的BeanUtils、Spring的BeanUtils、Cglib的BeanCopier、mapstruct都是浅拷贝。1.1深拷贝深拷贝:为基本数据类型传递值,为引用数据类型创建新对象,并复制其内容称为深拷贝。深拷贝常见的实现方式有四种:构造函数Serializable序列化实现Cloneable接口JSON序列化1.2浅拷贝浅拷贝:对于基本数据类型按值传递,对于引用数据类型按引用复制称为浅拷贝。浅克隆可以通过实现Cloneabe接口并覆盖Object类中的clone()方法来实现。2常用对象复制工具原理分析及性能比较目前常用的属性复制工具有Apache的BeanUtils、Spring的BeanUtils、Cglib的BeanCopier、mapstruct。ApacheBeanUtils:BeanUtils是Apachecommons组件的成员,是Apache提供的一组开源API,用于简化javaBean的操作,可以自动转换基本类型。SpringBeanUtils:BeanUtils是spring框架自带的工具。在org.springframework.beans包下,spring工程可以直接使用。CglibBeanCopier:cglib(CodeGenerationLibrary)是一个功能强大、高性能、高质量的代码生成库。BeanCopier依靠cglib的字节码增强能力,动态生成实现类,完成对象拷贝。mapstruct:mapstruct是一个Java注释处理器,用于生成类型安全的bean映射类。构造时根据注解生成实现类,完成对象拷贝。2.1原理分析2.1.1ApacheBeanUtils的使用方法:BeanUtils.copyProperties(target,source);BeanUtils.copyProperties对象复制的核心代码如下://1.获取源对象的属性描述PropertyDescriptor[]origDescriptors=this.getPropertyUtils().getPropertyDescriptors(orig);PropertyDescriptor[]temp=origDescriptors;intlength=origDescriptors.length;Stringname;Objectvalue;//2.循环获取源对象的各个属性,设置目标对象的属性值for(inti=0;iignoreList=ignoreProperties!=null?Arrays.asList(ignoreProperties):null;PropertyDescriptor[]arr$=targetPds;intlen$=targetPds.length;for(inti$=0;i$BeanCopier。Generator.create-\>AbstractClassGenerator.create->DefaultGeneratorStrategy.generate-\>BeanCopier.Generator.generateClassBeanCopier通过cglib动态代理操作字节码生成复制类,触发点为BeanCopier.create2.1.4mapstruct用法:导入pom的依赖声明转换接口mapstruct基于注解,构造时自动生成实现类。调用链如下:MappingProcessor.process->MappingProcessor.processMapperElementsMapperCreationProcessor.process:生成实现类MapperMapperRenderingProcessor:将实现类mapper写入文件,生成impl文件,使用需要声明转换接口,例如:@Mapper(nullValuePropertyMappingStrategy=NullValuePropertyMappingStrategy.IGNORE)publicinterfaceAirDepartTaskConvert{AirDepartTaskConvertINSTANCE=getMapper(AirDepartTaskConvert.class);AirDepartTaskDtoconvertToDto(AirDepartTaskairDepartConvertTask);}生成的实现类如下:tImpl实现AirDepartTaskConvert{@OverridepublicAirDepartTaskDtoconvertToDto(AirDepartTaskairDepartTask){if(airDepartTask==null){returnnull;}AirDepartTaskDtoairDepartTaskDto=newAirDepartTaskDto();airDepartTaskDto.setId(airDepartTask.getId());airDepartTaskDto.setTaskI(airDepartTaskDto.setTaskI.getTaskId());airDepartTaskDto.setPreTaskId(airDepartTask.getPreTaskId());Listlist=airDepartTask.getTaskBeginNodeCodes();if(list!=null){airDepartTaskDto.setTaskBeginNodeCodes(newArrayList(list));}//复制其他属性airDepartTaskDto.setYn(airDepartTask.getYn());returnairDepartTaskDto;}}2.2性能对比以航空业务系统中派送任务从po到dto的转换为例,作为复制数据量增加,研究复制数据耗时情况2.3复制选择经过以上分析,随着数据量的增加,整体耗时呈上升趋势。总的来说,ApacheBeanUtils的性能是最差的,日常使用中数据量不大的时候不建议使用。就spring、cglib、mapstruct而言,差别不大。spring框架下,推荐使用springbeanUtils。在不需要额外引入依赖包和大量数据的情况下,推荐使用涉及大量数据转换、属性映射、格式转换的cglib和mapstruct。可以,推荐使用mapstruct3Bestpractice3.1BeanCopier在转换同类型对象时可以使用mapcache减少create/***BeanCopier的次数er的缓存,避免频繁创建,高效复用*/privatestaticfinalConcurrentHashMapBEAN_COPIER_MAP_CACHE=newConcurrentHashMap();/***BeanCopier的copyBean,高性能推荐,增加缓存**@paramsource源文件*@paramtarget目标文件*/publicstaticvoidcopyBean(Objectsource,Objecttarget){Stringkey=genKey(source.getClass(),target.getClass());BeanCopierbeanCopier;if(BEAN_COPIER_MAP_CACHE.containsKey(key)){beanCopier=BEAN_COPIER_MAP_CACHE.get(key);}else{beanCopier=BeanCopier.create(source.getClass(),target.getClass(),false);BEAN_COPIER_MAP_CACHE.put(key,beanCopier);}beanCopier.copy(source,target,null);}/***不同类型的对象数据copylist**@paramsourceList*@paramtargetClass*@param*@return*/publicstaticListcopyListProperties(ListsourceList,ClasstargetClass)抛出异常{if(CollectionUtils.isNotEmpty(sourceList)){Listlist=newArrayList(sourceList.size());for(Objectsource:sourceList){Ttarget=copyProperties(source,targetClass);list.add(target);}returnlist;}returnLists.newArrayList();}/***返回不同类型的对象数据拷贝,使用注意该方法不能覆盖默认的无参构造方法**@paramsource*@paramtargetClass*@param*@return*/publicstaticTcopyProperties(Objectsource,ClasstargetClass)throwsException{Ttarget=targetClass.newInstance();copyBean(source,target);returntarget;}/***@paramsrcClazzsourceclass*@paramtgtClazztargetclass*@returnstring*/privatestaticStringgenKey(ClasssrcClazz,ClasstgtClazz){returnsrcClazz.getName()+tgtClazz.getName();}3.2mapstructmapstruct支持各种形式对象的映射,主要包括以下基本映射映射表达式。多个对象映射到一个对象映射集合映射映射枚举嵌套映射@Mapper(nullValuePropertyMappingStrategy=NullValuePropertyMappingStrategy.IGNORE)publicinterfaceAirDepartTaskConvert{AirDepartTaskConvertINSTANCE=getMapper(AirDepartTaskConvert.class);//a.基础映射@Mapping(target="createTime",source="updateTime")//b.映射表达式@Mapping(target="updateTimeStr",expression="java(newSimpleDateFormat(\"yyyy-MM-dd\").format(airDepartTask.getCreateTime()))")AirDepartTaskDtoconvertToDto(AirDepartTaskairDepartTask);}@MapperpublicinterfaceAddressMapper{AddressMapperINSTANCE=Mappers.getMapper(AddressMapper.class);//c.多个对像射到一个对像@Mapping(source="person.description",target="description")@Mapping(source="address.houseNo",target="houseNumber")DeliveryAddressDtopersonAndAddressToDeliveryAddressDto(Personperson,Addressaddress);}@MapperpublicinterfaceCarMapper{//d.射集合SetintegerSetToStringSet(Setintegers);ListcarsToCarDtos(Listcars);CarDtocarToCarDto(Carcar);//e.映射map@MapMapping(valueDateFormat="dd.MM.yyyy")MaplongDateMapToStringStringMap(Map酸ce);//f.映射枚举@ValueMappings({@ValueMapping(source="EXTRA",target="SPECIAL"),@ValueMapping(source="STANDARD",target="DEFAULT"),@ValueMapping(source="NORMAL",target="DEFAULT")})ExternalOrderTypeorderTypeToExternalOrderType(OrderTypeorderType);//g.嵌套映射@Mapping(target="fish.kind",source="fish.type")@Mapping(target="fish.name",ignore=true)@Mapping(target="ornament",source="interior.ornament")@Mapping(target="material.materialType",source="material")@Mapping(target="quality.report.organisation.name",source="quality.report.organisationName")FishTankDtomap(FishTanksource);}4小结以上是我对使用对象复制的过程的简单讨论。在日常的系统开发过程中,要深入底层逻辑,哪怕是发现一个小小的改动,能让我们的系统更加稳定流畅,也是值得改进的。最后希望随着我们的加入,系统会越来越稳定流畅,我们也会越来越好。