在日常开发中,我们会定义多种不同的Javabean,比如DTO(Data Transfer Object:数据传输对象),DO(Data Object:数据库映射对象,与数据库一一映射),VO(View Object:显示层对象,通常是 Web 向模板渲染引擎层传输的对象)等等这些对象。在这些对象与对象之间转换通常是调对象的set和get方法进行复制,这种转换通常也是很无聊的操作,如果有一个专门的工具来解决Javabean之间的转换问题,让我们从这种无聊的转换操作中解放出来。
首先梳理出来现在有哪些对象拷贝的方式:
- 浅拷贝
- Apache的BeanUtils:
基于反射实现
,BeanUtils是Apache commens组件里面的成员,由Apache提供的一套开源api,用于简化对javaBean的操作,能够对基本类型自动转换。 - Spring的BeanUtils:
基于反射实现
,BeanUtils是Spring框架提供的对Java反射和自省API的包装。其主要目的是利用反射机制对JavaBean的属性进行处理。 - BeanCopier:
动态代理实现
,BeanCopier是Cglib包中的一个类,用于对象的复制。目标对象必须先实例化 而且对象必须要有setter方法。由于BeanCopier是动态代理实现所以性能上比前两个要好的多
。
- 深拷贝
- Mapstruct:MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。MapStruct具有以下优点:
- 速度快:使用普通的方法代替反射
- 编译时类型安全性 : 只能映射彼此的对象和属性,不会将商品实体意外映射到用户DTO等
数据量 | Apache | Spring | MapStruct | BeanCopier |
100w | 391ms | 250ms | 45ms | 57ms |
10w | 82ms | 34ms | 8ms | 10ms |
1w | 30ms | 19ms | 2ms | 7ms |
1k | 15ms | 6ms | 1ms | 5ms |
100 | 5ms | 3ms | 1ms | 4ms |
10 | 2ms | 1ms | 1ms | 4ms |
根据测试结果,我们可以得出在速度方面,MapStruct是最好的,执行速度是 Apache BeanUtils 的10倍、Spring BeanUtils 的 4-5倍、和 BeanCopier 的速度差不多。
由此可以看出,在大数据量级的情况下,MapStruct 和 BeanCopier 都有着较高的性能优势,其中 MapStruct 尤为优秀。如果你仅是在日常处理少量的对象时,选取哪个其实变得并不重要,但数据量大时建议还是使用MapStruct 或 BeanCopier 的方式,提高接口性能。
实战示例
maven依赖引入
方式1
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.3.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Final</version>
<scope>provided</scope>
</dependency>
方式2
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<build>
<!-- 配置lombok 和mapStruct注解处理器 -->
<pluginManagement>
<plugins>
<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>
<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.12</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
实体对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
// 账号
private String account;
// 金额
private Integer amt;
}
@Data
@AllArgsConstructor
public class User {
private String userName;
private List<String> addresses;
}
@Data
public class CreateAccountDTO {
private String key;
private List<Account> accounts;
}
@Data
public class CreateAccountVO {
private String visitKey;
private User user;
private List<Account> accounts;
}
转换
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface AccountConvertUtils {
AccountConvertUtils INSTANCE = Mappers.getMapper(AccountConvertUtils.class);
// 普通的映射
CreateAccountVO convert(CreateAccountDTO createAccountDTO);
// 类型转换的映射
@Mappings({
@Mapping(target = "visitKey", source = "key"),
})
CreateAccountVO convertWithMapping(CreateAccountDTO createAccountDTO);
// 多对一映射
@Mappings({
@Mapping(target = "visitKey", source = "createAccountDTO.key"),
})
CreateAccountVO convert(User user, CreateAccountDTO createAccountDTO);
}
测试用例
public class DemoApp {
public static void main(String[] args) {
CreateAccountDTO createAccountDTO = new CreateAccountDTO();
List<Account> accounts = new ArrayList<>();
accounts.add(new Account("ACT2021051200000001", 1000));
accounts.add(new Account("ACT2021051200000002", 5000));
createAccountDTO.setKey("326D1478E3914C8A");
createAccountDTO.setAccounts(accounts);
User user = new User("Jaemon", Lists.newArrayList("SZ", "NS"));
// 执行1
// CreateAccountVO createAccountVO = AccountConvertUtils.INSTANCE.convert(createAccountDTO);
// 执行2
// CreateAccountVO createAccountVO = AccountConvertUtils.INSTANCE.convertWithMapping(createAccountDTO);
// 执行3
CreateAccountVO createAccountVO = AccountConvertUtils.INSTANCE.convert(user, createAccountDTO);
createAccountDTO.getAccounts().add(new Account("ACT2021051200000003", 1010));
System.out.println(createAccountDTO.toString());
System.out.println(createAccountVO.toString());
}
}
运行输出
// 执行1打印
CreateAccountDTO(key=326D1478E3914C8A, accounts=[Account(account=ACT2021051200000001, amt=1000), Account(account=ACT2021051200000002, amt=5000), Account(account=ACT2021051200000003, amt=1010)])
CreateAccountVO(visitKey=null, user=null, accounts=[Account(account=ACT2021051200000001, amt=1000), Account(account=ACT2021051200000002, amt=5000)])
// 执行2打印
CreateAccountDTO(key=326D1478E3914C8A, accounts=[Account(account=ACT2021051200000001, amt=1000), Account(account=ACT2021051200000002, amt=5000), Account(account=ACT2021051200000003, amt=1010)])
CreateAccountVO(visitKey=326D1478E3914C8A, user=null, accounts=[Account(account=ACT2021051200000001, amt=1000), Account(account=ACT2021051200000002, amt=5000)])
// 执行3打印
CreateAccountDTO(key=326D1478E3914C8A, accounts=[Account(account=ACT2021051200000001, amt=1000), Account(account=ACT2021051200000002, amt=5000), Account(account=ACT2021051200000003, amt=1010)])
CreateAccountVO(visitKey=326D1478E3914C8A, user=User(userName=Jaemon, addresses=[SZ, NS]), accounts=[Account(account=ACT2021051200000001, amt=1000), Account(account=ACT2021051200000002, amt=5000)])
References
- mapstruct官网
- mapstruct github
- cglib github
- 使用cglib进行bean拷贝
- 对象拷贝之Apache BeanUtils、Spring的BeanUtils、Mapstruct、BeanCopier、PropertieyUtils对比(深拷贝
- 【教程】如何利用MapStruct 解决对象之间转换问题(一)