Java对象拷贝原理剖析及最佳实践

2023-01-16 0 881

原副标题:Java第一类复本基本上原理探究及最差课堂教学

译者 | 天猫云合作开发人员-象山翔

书名镜像:https://my.oschina.net/u/4090830/blog/5598304

1 序言

第一类复本,是他们在合作开发操作过程中,绕不行的操作过程,既存有于 Po、Dto、Do、Vo 各整体表现层统计数据的切换,也存有于控制系统可视化如格式化、反格式化。

Java 第一类复本分成深复本和浅复本,现阶段常用的特性复本辅助工具,主要包括 Apache 的 BeanUtils、Spring 的 BeanUtils、Cglib 的 BeanCopier、mapstruct 都是浅复本。

1.1 深复本

深复本:对基本上正则表达式进行值传达,对提及正则表达式,建立两个捷伊第一类,并拷贝其文本称作深复本。

深复本常用有下列三种同时实现形式:

缺省 Serializable 格式化 同时实现 Cloneable USB JSON 格式化

Java对象拷贝原理剖析及最佳实践

1.2 浅复本

浅复本:对基本上正则表达式展开值传达,对提及正则表达式展开提及传达般的复本称作浅复本。通过同时实现 Cloneabe USB并重写 Object 类中的 clone 方法可以同时实现浅克隆。

Java对象拷贝原理剖析及最佳实践

2 常用第一类复本辅助工具基本上原理探究及性能对比

现阶段常用的特性复本辅助工具,主要包括 Apache 的 BeanUtils、Spring 的 BeanUtils、Cglib 的 BeanCopier、mapstruct。

Apache BeanUtils:BeanUtils 是 Apache commons 组件里面的成员,由 Apache 提供的一套开源 api,用于简化对 javaBean 的操作,能够对基本上类型自动切换。 Spring BeanUtils:BeanUtils 是 spring 框架下自带的辅助工具,在 org.springframework.beans 包下, spring 项目可以直接使用。 Cglib BeanCopier:cglib(Code Generation Library)是两个强大的、高性能、高质量的代码生成类库,BeanCopier 依托于 cglib 的字节码增强能力,动态生成同时实现类,完成第一类的复本。 mapstruct:mapstruct 是两个 Java 注释处理器,用于生成类型安全的 bean 映射类,在构建时,根据注解生成同时实现类,完成第一类复本。2.1 基本上原理分析

2.1.1 Apache BeanUtils

使用形式:BeanUtils.copyProperties (target, source);

BeanUtils.copyProperties 第一类复本的核心代码如下:

PropertyDeor[] origDeors = this.getPropertyUtils.getPropertyDeors(orig);

PropertyDeor[] temp = origDeors;

intlength = origDeors.length;

String name;

Object value;

第一类每个特性,设置目标第一类特性值

for( inti = 0; i < length; ++i) {

PropertyDeor origDeor = temp[i];

name = origDeor.getName;

// 3.校验源第一类字段可读切目标第一类该字段可写

if(! “class”. equals(name) && this.getPropertyUtils.isReadable(orig, name) &&this.getPropertyUtils.isWriteable(dest, name)) {

try{

value= this.getPropertyUtils.getSimpleProperty(orig, name);

// 5.复本特性

this.copyProperty(dest, name, value);

} catch(NoSuchMethodException var10) {

}

}

}

循环遍历源第一类的每个特性,对于每个特性,复本流程为:

校验目标类的字段是否可写 isWriteable

pleProperty

设置目标类字段的值

PropertyDeor[] deors = this.getPropertyDeors(bean);

if(deors != null) {

for(int i = 0; i < deors.length; ++i) {

if(name.equals(deors[i].getName)) {

returndeors[i];

}

}

}

2.1.2 Spring BeanUtils

使用形式: BeanUtils.copyProperties (source, target);

BeanUtils.copyProperties 核心代码如下:

PropertyDeor[] targetPds = getPropertyDeors(actualEditable);

List<String> ignoreList = ignoreProperties !=null? Arrays.asList(ignoreProperties) : null;

PropertyDeor[] arr$ = targetPds;

intlen$ = targetPds.length;

for( inti$ = 0; i$ < len$; ++i$) {

PropertyDeor targetPd = arr$[i$];

Method writeMethod = targetPd.getWriteMethod;

if(writeMethod != null&& (ignoreList == null|| !ignoreList.contains(targetPd.getName))) {

PropertyDeor sourcePd = getPropertyDeor(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 value= readMethod.invoke(source);

if(!Modifier.isPublic(writeMethod.getDeclaringClass.getModifiers)) {

writeMethod.setAccessible( true);

}

writeMethod.invoke(target, value);

} catch(Throwable var15) {

thrownewFatalBeanException( “Could not copy property “+ targetPd.getName + ” from source to target”, var15);

}

}

}

}

}

复本流程简要描述如下:

写目标特性值

2.1.3 Cglib BeanCopier

使用形式:

BeanCopier beanCopier = BeanCopier.create(AirDepartTask .class, AirDepartTaskDto.class, false);

beanCopier.copy(airDepartTask, airDepartTaskDto, null);

create 调用链如下:

BeanCopier.create

-> BeanCopier.Generator.create

-> AbstractClassGenerator.create

->DefaultGeneratorStrategy.generate

-> BeanCopier.Generator.generateClass

BeanCopier 通过 cglib 动态代理操作字节码,生成两个拷贝类,触发点为 BeanCopier.create

Java对象拷贝原理剖析及最佳实践

2.1.4 mapstruct

使用形式:

引入 pom 依赖 声明切换USB

mapstruct 基于注解,构建时自动生成同时实现类,调用链如下:

MappingProcessor.process -> MappingProcessor.processMapperElements

MapperCreationProcessor.process: 生成同时实现类 Mapper

MapperRenderingProcessor: 将同时实现类 mapper,写入文件,生成 impl 文件

使用时需要声明切换USB,例如:

@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)

publicinterfaceAirDepartTaskConvert{

AirDepartTaskConvert INSTANCE = getMapper(AirDepartTaskConvert.class);

AirDepartTaskDto convertToDto(AirDepartTask airDepartTask);

}

生成的同时实现类如下:

publicclassAirDepartTaskConvertImplimplementsAirDepartTaskConvert{

@Override

publicAirDepartTaskDto convertToDto(AirDepartTask airDepartTask) {

if( airDepartTask == null) {

returnnull;

}

AirDepartTaskDto airDepartTaskDto =newAirDepartTaskDto;

airDepartTaskDto.setId( airDepartTask.getId );

airDepartTaskDto.setTaskId( airDepartTask.getTaskId );

airDepartTaskDto.setPreTaskId( airDepartTask.getPreTaskId );

List<String> list= airDepartTask.getTaskBeginNodeCodes;

if( list!= null) {

airDepartTaskDto.setTaskBeginNodeCodes( newArrayList<String>(list) );

}

// 其他特性复本

airDepartTaskDto.setYn( airDepartTask.getYn );

returnairDepartTaskDto;

}

}

2.2 性能对比

以航空业务控制系统中发货任务 po 到 dto 切换为例,随着复本统计数据量的增大,研究复本统计数据耗时情况

Java对象拷贝原理剖析及最佳实践

2.3 复本选型

经过以上分析,随着统计数据量的增大,耗时整体呈上升趋势

整体情况下,Apache BeanUtils 的性能最差,日常使用操作过程中不建议使用 在统计数据规模不大的情况下,spring、cglib、mapstruct 差异不大,spring 框架下建议使用 spring 的 beanUtils,不需要额外引入依赖包 统计数据量大的情况下,建议使用 cglib 和 mapstruct 涉及大量统计数据切换,特性映射,格式切换的,建议使用 mapstruct3 最差课堂教学 3.1 BeanCopier

使用时可以使用 map 缓存,减少同一类第一类切换时,create 次数

/**

* BeanCopier的缓存,避免频繁建立,高效复用

*/

privatestaticfinalConcurrentHashMap<String, BeanCopier> BEAN_COPIER_MAP_CACHE = newConcurrentHashMap<String, BeanCopier>;

/**

* BeanCopier的copyBean,高性能推荐使用,增加缓存

*

* @paramsource 源文件的

* @paramtarget 目标文件

*/

publicstaticvoid copyBean(Object source, Object target) {

String key = genKey(source.getClass, target.getClass);

BeanCopier beanCopier;

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<T>

* @return

*/

publicstatic<T> List<T> copyListProperties( List<?> sourceList,Class<T> targetClass) throwsException{

if(CollectionUtils.isNotEmpty(sourceList)) {

List<T> list= newArrayList<T>(sourceList.size);

for(Object source : sourceList) {

T target = copyProperties(source, targetClass);

list.add(target);

}

returnlist;

}

returnLists.newArrayList;

}

/**

* 返回不同类型第一类统计数据copy,使用此方法需注意不能覆盖默认的无参构造方法

*

* @paramsource

* @paramtargetClass

* @param<T>

* @return

*/

publicstatic<T> T copyProperties(Object source,Class<T> targetClass) throwsException{

T target = targetClass.newInstance;

copyBean(source, target);

returntarget;

}

/**

* @paramsrcClazz 源class

* @paramtgtClazz 目标class

* @returnstring

*/

privatestaticString genKey(Class<?> srcClazz, Class<?> tgtClazz) {

returnsrcClazz.getName + tgtClazz.getName;

}

3.2 mapstruct

mapstruct 支持多种形式第一类的映射,主要有下面几种

基本上映射 映射表达式 多个第一类映射到两个第一类 映射集合 映射 map 映射枚举 嵌套映射 @Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)

publicinterfaceAirDepartTaskConvert{

AirDepartTaskConvert INSTANCE = getMapper(AirDepartTaskConvert .class);

// a.基本上映射

@Mapping(target =“createTime”, source = “updateTime”)

// b.映射表达式

@Mapping(target = “updateTimeStr”, expression = “java(new SimpleDateFormat( \”yyyy-MM-dd\” ).format(airDepartTask.getCreateTime))”)

AirDepartTaskDto convertToDto(AirDepartTask airDepartTask);

}

@Mapper

publicinterfaceAddressMapper{

AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);

// c.多个第一类映射到两个第一类

@Mapping(source = “person.deion”, target = “deion”)

@Mapping(source = “address.houseNo”, target = “houseNumber”)

DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);

}

@Mapper

publicinterfaceCarMapper{

// d.映射集合

Set<String> integerSetToStringSet(Set<Integer> integers);

List<CarDto> carsToCarDtos(List<Car> cars);

CarDto carToCarDto(Car car);

// e.映射map

@MapMapping(valueDateFormat = “dd.MM.yyyy”)

Map<String,String> longDateMapToStringStringMap(Map<Long, Date> source);

// f.映射枚举

@ValueMappings({

@ValueMapping(source = “EXTRA”, target =“SPECIAL”),

@ValueMapping(source = “STANDARD”, target = “DEFAULT”),

@ValueMapping(source = “NORMAL”, target = “DEFAULT”)

})

ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);

// 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”)

FishTankDto map( FishTank source );

}

4 总结

以上就是我在使用第一类复本操作过程中的一点浅谈。在日常控制系统合作开发操作过程中,要深究底层逻辑,哪怕发现一小点的改变能够使他们的控制系统更加稳定、顺畅,都是值得他们去改进的。

最后,希望随着他们的加入,控制系统会更加稳定、顺畅,他们会变得越来越优秀。

你参与开源吗?

抽开源中国周边啦~

END

合作开发人员必备的Firfox插件

这里有最新开源资讯、软件更新、技术干货等文本

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦~

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务