在使用分层或者分模块化的项目中,我们可能定义各种各样的O,例如:DO,VO,DTO等等。我们在进行这些对象之间的拷贝时,通过手动写get/set方法进行属性之间的赋值。因为他们之间的属性大部分都是相同的,不仅浪费时间,并且还有大量重复代码。所以,各种框架都添加的对象之间的拷贝的工具类。例如:
Spring自带了BeanUtils
Apatch自带的BeanUtils
Apatch自带的PropertyUtils
mapstruct提供Mappers
本文主要介绍MapStruct的基础知识、Mapstruct的优缺点、Mapctruct的拷贝示例、以及四种方法时间对比。
https://mapstruct.org
https://mapstruct.org/documentation/stable/reference/html/
https://github.com/mapstruct/mapstruct-examples
mapstruct的作用:就像mapstruct官网所说的:mapsrtuct是一个用于简化在JavaBean之间映射的代码生成器工具,并且是基于约定高于配置的方式。
因为生成的代码使用简单的get/set方法,所以mapstruct可以更快执行、类型之间复制更安全、且容易理解。
下面看看mapstruct相比其他映射工具有什么优点
mapstruct是在代码编译的时候,生成其映射规则的代码,所以会在编译时暴露映射错误的代码,让错误提前暴露。
因为使用的是一些简单方法:set/get或者Fluent方式,而非反射方式,所以可以更快的执行完成。可以通过04章节查看各个方法效率上对比
可以实现深拷贝,上面4种方法中只有mapstruct可以实现深拷贝。但是需要进行配置(使用@mapper(mappingControl=DeepClone.class)进行设置)。其他的都是浅拷贝。从而mapstruct实现修改新对象不会对老对象产生影响。
类型更加安全
可以进行自定义的映射。可以自定义各种方法,完成属性映射。例如:将两个属性数据合并成一个设置到目标属性上
必须添加一个接口或者抽象类,才能实现映射。其他的三种方法都不用写额外接口或者类。
上面主要是介绍一下Mapstruct的基础知识,下面看一个mapstruct的简单示例,完成属性之间的映射。
在示例中,我们使用mapstruct的1.4.2.Final版本,进行试验的
//第一个拷贝对象
public class CompareA {
private String name;
private Integer age;
private LocalDateTime createTime;
private double score;
private Double totalScore;
private Date updateTIme;
private ChildCompare childCompare;
private Long days;
//省略其get/set方法,也可使用Lombok,以后讲解lombok与mapstruct结合使用
}
public class CompareB {
private String name;
private Integer age;
private String createTime;
private double score;
private Double totalScore;
private Date updateTIme;
private ChildCompare childCompare;
private Long day;
//省略其get/set方法,
}
//这是CompareA和CompareB共有的对象
public class ChildCompare {
private String childName;
private long childAge;
//省略其get/set方法,
}
上面是两个实体对象,观察这两个实体可以看到有两点不同:
package com.moxiao.properties.mapstruct;
import com.moxiao.properties.entity.CompareA;
import com.moxiao.properties.entity.CompareB;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.control.DeepClone;
import org.mapstruct.factory.Mappers;
/**
* @author moxiao
*/
@Mapper
public interface CompareMapper {
CompareMapper INSTANCE = Mappers.getMapper(CompareMapper.class);
@Mapping(target = "day", source = "days")
@Mapping(target = "createTime", source = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(target = "childCompare", source = "childCompare", mappingControl = DeepClone.class)
CompareB mapper(CompareA compareA);
}
从上面的实现中,我们加入了三个注解:
package com.moxiao.properties.mapstruct;
import com.moxiao.properties.entity.ChildCompare;
import com.moxiao.properties.entity.CompareA;
import com.moxiao.properties.entity.CompareB;
import java.time.format.DateTimeFormatter;
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor"
)
public class CompareMapperImpl implements CompareMapper {
@Override
public CompareB mapper(CompareA compareA) {
if ( compareA == null ) {
return null;
}
CompareB compareB = new CompareB();
compareB.setDay( compareA.getDays() );
if ( compareA.getCreateTime() != null ) {
compareB.setCreateTime( DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" ).format( compareA.getCreateTime() ) );
}
compareB.setChildCompare( childCompareToChildCompare( compareA.getChildCompare() ) );
compareB.setName( compareA.getName() );
compareB.setAge( compareA.getAge() );
compareB.setScore( compareA.getScore() );
compareB.setTotalScore( compareA.getTotalScore() );
compareB.setUpdateTIme( compareA.getUpdateTIme() );
return compareB;
}
protected ChildCompare childCompareToChildCompare(ChildCompare childCompare) {
if ( childCompare == null ) {
return null;
}
ChildCompare childCompare1 = new ChildCompare();
childCompare1.setChildName( childCompare.getChildName() );
childCompare1.setChildAge( childCompare.getChildAge() );
return childCompare1;
}
}
从上面的实现,我们可以看出,添加一个childCompareToChildCompare方法,来完成对象之间的深度拷贝。同样的将days设置到了day属性,等等。
CompareMapper.INSTANCE.mapper(new CompareA());
我们在1,000,000个对象进行各个工具之间的对比,看看效率如何?
package com.moxiao.properties.mapstruct;
import com.moxiao.properties.entity.ChildCompare;
import com.moxiao.properties.entity.CompareA;
import com.moxiao.properties.entity.CompareB;
import org.apache.commons.beanutils.PropertyUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StopWatch;
import java.lang.reflect.InvocationTargetException;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @author moxiao
*/
public class CompareTest {
int count = 1000000;
/**
* Spring中的BeanUtils的使用,最后使用了6700ms
*/
@Test
public void springBeanUtils() {
CompareA populate = populate();
StopWatch stopWatch = new StopWatch();
stopWatch.start("开始拷贝");
for (int i = 0; i < count; i++) {
CompareB compareB = new CompareB();
BeanUtils.copyProperties(populate, compareB);
}
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis());
}
/**
* 这是使用mapstruct进行对象拷贝,使用时间54ms
*/
@Test
public void mapstructBeanUtils() {
CompareA populate = populate();
StopWatch stopWatch = new StopWatch();
stopWatch.start("开始拷贝");
for (int i = 0; i < count; i++) {
CompareB mapper = CompareMapper.INSTANCE.mapper(populate);
}
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis());
}
/**
* 这是Apache的BeanUtils工具,使用7086ms
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@Test
public void commonBeanUtils() throws InvocationTargetException, IllegalAccessException {
CompareA populate = populate();
StopWatch stopWatch = new StopWatch();
stopWatch.start("开始拷贝");
for (int i = 0; i < count; i++) {
CompareB compareB = new CompareB();
org.apache.commons.beanutils.BeanUtils.copyProperties(compareB, populate);
}
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis());
}
/**
* 使用Apache中的propertyUtils静态方法,使用3756ms
* @throws IllegalAccessException
* @throws NoSuchMethodException
* @throws InvocationTargetException
*/
@Test
public void commonPropertiesUtils() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
CompareA populate = populate();
StopWatch stopWatch = new StopWatch();
stopWatch.start("开始拷贝");
for (int i = 0; i < count; i++) {
CompareB compareB = new CompareB();
PropertyUtils.copyProperties(compareB, populate);
}
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis());
}
public CompareA populate() {
CompareA compareA = new CompareA();
compareA.setAge(10);
compareA.setName("moxiao");
compareA.setCreateTime(LocalDateTime.now());
compareA.setDays(100L);
compareA.setTotalScore(100.0);
compareA.setScore(12.3);
compareA.setUpdateTIme(new Date());
ChildCompare childCompare = new ChildCompare();
childCompare.setChildAge(200);
childCompare.setChildName("moxiaoChild");
compareA.setChildCompare(childCompare);
return compareA;
}
}
pom文件如下:
4.0.0
com.moxiao
propertiescopytest
1.0-SNAPSHOT
8
8
org.springframework
spring-context
5.3.9
org.springframework
spring-beans
5.3.9
org.mapstruct
mapstruct
1.4.2.Final
org.projectlombok
lombok
1.18.18
provided
commons-beanutils
commons-beanutils
1.9.4
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
1.8
org.projectlombok
lombok-mapstruct-binding
0.2.0
org.mapstruct
mapstruct-processor
1.4.2.Final
org.projectlombok
lombok
1.18.18
true
-Amapstruct.suppressGeneratorTimestamp=true
-Amapstruct.suppressGeneratorVersionInfoComment=true
上面是各个工具之间的对比,1000000数据之间拷贝的时间为:
工具名称 | 时间(ms) |
mapstruct | 54 |
Spring的BeanUtils | 6700 |
Apache的common的BeanUtils | 7086 |
Apache的common的PropertyUtils | 3756 |
综上所述,1百万数据,mapstruct可以在50ms左右完成。
这篇文章只是Mapstruct文章的开头,后续还有: