java 性能最好的对象、列表深拷贝框架MapStruct(一秒拷贝百万次)
java 性能最好的对象、列表深拷贝框架MapStruct
MapStruct是一款非常实用Java工具,主要用于解决对象之间的拷贝问题,比如PO/DTO/VO/QueryParam之间的转换问题。区别于BeanUtils这种通过反射,它通过编译器编译生成常规方法,将可以很大程度上提升效率。
!!!注意:在常用场景下你可以认为是深拷贝的,但是你仔细去看,其实并不是完全深拷贝的,它的拷贝和Spring BeanUtils是一个样的,那就是第一层的普通对象确实是深拷贝,如果你属性里是引用对象的(比如List),那这种字段的拷贝其实是浅拷贝的,只拷贝了引用。但是它这种拷贝方式,在平时业务都是够用的,且性能高,如果要完全深拷贝请使用序列化反序列化的方式来做。例如FastJSON
-
JavaBean 问题引入
在开发的时候经常会有业务代码之间有很多的 JavaBean 之间的相互转化,比如PO/DTO/VO/QueryParam之间的转换问题。之前我们的做法是: -
常见拷贝技术
org.apache.commons.beanutils.PropertyUtils.copyProperties
org.apache.commons.beanutils.BeanUtils.copyProperties
org.springframework.beans.BeanUtils.copyProperties
net.sf.cglib.beans.BeanCopier -
纯get/set
辅助IDE插件拷贝对象时可以自动set所有方法字段 (这种方式可能有些开发人员不清楚)
不仅看上去冗余添加新的字段时依然需要手动
开发效率比较低 -
MapStruct 带来的改变
MapSturct 是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。
工具可以帮我们实现 JavaBean 之间的转换, 通过注解的方式。
同时, 作为一个工具类,相比于手写, 其应该具有便捷, 不容易出错的特点。
- MapStrcut实现的原理?
MapStruct 来生成的代码, 其类似于人手写。 速度上可以得到保证。
前面例子中生成的代码可以在编译后看到, 在 target/generated-sources/annotations 里可以看到; 同时真正在代码包执行的可以在target/classes包中看到。
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2024-01-23T16:05:23+0800",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
public class ABMapperImpl implements ABMapper {
@Override
public BObject mapAtoB(AObject aObject) {
if ( aObject == null ) {
return null;
}
BObject bObject = new BObject();
bObject.setA( aObject.getA() );
bObject.setB( aObject.getB() );
bObject.setC( aObject.getC() );
bObject.setD( aObject.getD() );
bObject.setE( aObject.getE() );
bObject.setF( aObject.getF() );
bObject.setG( aObject.getG() );
bObject.setH( aObject.getH() );
bObject.setI( aObject.getI() );
List<ItemVo> list = aObject.getList();
if ( list != null ) {
bObject.setList( new ArrayList<ItemVo>( list ) );
}
return bObject;
}
}
测试环境
项目:jdk1.8、maven3.6、idea编辑器、springboot2.2.x
测试对象
/**
* @author: humorchen
* date: 2024/1/22
* description:
**/
@Data
public class AObject {
private String a;
private Integer b;
private Long c;
private Double d;
private Float e;
private Boolean f;
private Character g;
private Byte h;
private Short i;
private List<ItemVo> list;
}
/**
* @author: humorchen
* date: 2024/1/22
* description:
**/
@Data
public class BObject {
private String a;
private Integer b;
private Long c;
private Double d;
private Float e;
private Boolean f;
private Character g;
private Byte h;
private Short i;
private List<ItemVo> list;
}
/**
* @author: humorchen
* date: 2024/1/22
* description:
**/
@Data
public class ItemVo {
private String a;
private Integer b;
}
测试样例对象
/**
* 获取AObject
*
* @return
*/
public static AObject getA() {
AObject aObject = new AObject();
aObject.setA("a");
aObject.setB(1);
aObject.setC(1L);
aObject.setD(1d);
aObject.setE(1f);
aObject.setF(true);
aObject.setG('g');
aObject.setH((byte) 1);
aObject.setI((short) 1);
List<ItemVo> objects = new ArrayList<>();
ItemVo itemVo = new ItemVo();
itemVo.setA("1");
itemVo.setB(1);
objects.add(itemVo);
aObject.setList(objects);
return aObject;
}
测试次数:执行100万次复制
运行设备CPU:Intel® Core™ i7-9700 CPU @ 3.00GHz 3.00 GHz 8核心8线程,内存16GB
MapStruct
相对麻烦点,要写mapper接口,然后会在编译的时候生成接口实现类的字节码文件,相当于你定义接口,它帮你实现手写set方法代码去复制值,执行时没有用反射了,效率更高
maven依赖
mapstruct 1.4.2.Final版本
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
额外需要配置maven的build,不配置这个执行的时候会报错找不到实现类,因为编译构建期间没有给你生成实现类的字节码,我这里还使用了lombok(1.18.6版本),不懂 lombok的可以百度下,很简单。
<build>
<finalName>recovery-operation</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- See https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html -->
<!-- Classpath elements to supply as annotation processor path. If specified, the compiler -->
<!-- will detect annotation processors only in those classpath elements. If omitted, the -->
<!-- default classpath is used to detect annotation processors. The detection itself depends -->
<!-- on the configuration of annotationProcessors. -->
<!-- -->
<!-- According to this documentation, the provided dependency processor is not considered! -->
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
百万次拷贝耗时998ms
测试代码如下
/**
* testMapperCopy
* MapperCopy:998 ms
*/
@Test
public void testMapperCopy() {
ArrayList<AObject> aObjects = new ArrayList<>();
for (int i = 0; i < max; i++) {
aObjects.add(getA());
}
StopWatch stopWatch = new StopWatch();
stopWatch.start();
List<BObject> bObjects = aObjects.stream().map(ABMapper.INSTANCE::mapAtoB).collect(Collectors.toList());
stopWatch.stop();
System.out.println("MapperCopy:" + stopWatch.getTotalTimeMillis() + " ms");
}
Github地址
MapStruct https://github.com/mapstruct/mapstruct
深度了解推荐阅读
常用开发库 - MapStruct工具库详解 https://zhuanlan.zhihu.com/p/357402881?utm_id=0
MapStruct官网
本文来自博客园,作者:HumorChen99,转载请注明原文链接:https://www.cnblogs.com/HumorChen/p/18039420