关于对象间复制应该是比较常见的了,一个对象O可以衍生出DO、DTO、VO等;一般比较简单的场景我们可以选择使用BeanUtils,或者spring官方的,或者其他第三方的,大同小异,但是比较复杂的字段映射这个工具类就不够用了,而且在批量转换的时候,效率也是一个问题;这个时候,MapStruct就能完美兼容这2个问题,在编译的时候帮你自动生成映射代码,而且效率翻倍。
安装
官方手册:https://mapstruct.org/documentation/stable/reference/html
这个有个小问题,使用官方推荐plugin的方式引入时,与lombok产生了冲突,估计是自动生成get/set方法的缘故。故改为以下依赖:
<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
使用
建立一个源对象
import lombok.Data;
@Data
public class User {
private Long userId;// 用户ID
private String userName;// 用户名
private String nickName;// 用户昵称
private String sex;// 用户性别
private String status;// 状态
}
建立一个需要复制的对象
import lombok.Data;
import lombok.ToString;
@Data
public class UserDto {
private String nickName;// 用户昵称
private String sex;// 用户性别
private String status;// 状态
}
新建一个字段映射接口,这里只是简单的映射,事实上,这里可以进行复杂的字段映射,比如2个对象字段名不一样,字段类型不一样,都是可以自定义的;
@Mapper()这里加上componentModel = "spring"表示该接口自动注入spring的IOC容器中,后续可以通过@Autowired装配
import com.example.mapstruct.bean.User;
import com.example.mapstruct.bean.UserDto;
import org.mapstruct.Mapper;
import java.util.List;
/**
* 字段映射接口
*
* @author jason
*/
@Mapper(componentModel = "spring")
public interface UserConvert {
/**
* 单个对象互转
*
* @param user
* @return
*/
UserDto user2UserDto(User user);
/**
* 数组互转
*
* @param userList
* @return
*/
List<UserDto> userList2UserDtoList(List<User> userList);
}
测试
import com.example.mapstruct.bean.User;
import com.example.mapstruct.bean.UserDto;
import com.example.mapstruct.convert.UserConvert;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@SpringBootTest
class MapStructApplicationTests {
@Autowired
private UserConvert userConvert;
@Test
void contextLoads() {
// 准备count笔测试数据
int count = 100 * 10000;
List<User> userList = getUserList(count);
// 单个转换
long star = System.currentTimeMillis();
UserDto userDto = userConvert.user2UserDto(userList.get(0));
long end = System.currentTimeMillis();
log.info("单个转换耗时:{}ms", end - star);
// 批量转换
star = System.currentTimeMillis();
List<UserDto> userDtoList = userConvert.userList2UserDtoList(userList);
end = System.currentTimeMillis();
log.info("{}笔批量转换耗时:{}ms", count, end - star);
}
/**
* 获取count笔测试数据
*
* @param count
* @return
*/
private List<User> getUserList(int count) {
List<User> userList = new ArrayList<>();
for (int i = 0; i < count; i++) {
User user = new User();
user.setUserId(20201117L);
user.setUserName("z1353095373");
user.setNickName("复制粘贴");
user.setSex("男");
user.setStatus("active");
userList.add(user);
}
return userList;
}
}
测试环境:本地win10,i7,16G
100w笔数据复制一次,仅20ms,可以说是相当不错了
另外,程序运行后,可以在编译后的文件夹看到MapStruct生成的实现类代码
再参考一下网友测的
工具 | 十个对象复制1次 | 一万个对象复制1次 | 一百万个对象复制1次 | 一百万个对象复制5次 |
---|---|---|---|---|
mapStruct | 0ms | 3ms | 96ms | 281ms |
hutools的BeanUtil | 23ms | 102ms | 1734ms | 8316ms |
spring的BeanUtils | 2ms | 47ms | 726ms | 3676ms |
apache的BeanUtils | 20ms | 156ms | 10658ms | 52355ms |
apache的PropertyUtils | 5ms | 68ms | 6767ms | 30694ms |
看了这个数据,也难怪阿里巴巴在Java开发手册中明令禁止使用Apache BeanUtils了