丢弃 BeanUtils!MapStruct真香!
personDTO.setName(personDO.getName());
personDTO.setAge(personDO.getAge());
personDTO.setSex(personDO.getSex());
personDTO.setBirthday(personDO.getBirthday());
MapStruct(https://mapstruct.org/ )是一种代码生成器,它极大地简化了基于"约定优于配置"方法的Java bean类型之间映射的实现。生成的映射代码使用纯方法调用,因此快速、类型安全且易于理解。
约定优于配置,也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。
假设我们有两个类需要进行互相转换,分别是PersonDO和PersonDTO,类定义如下:
public class PersonDO {
private Integer id;
private String name;
private int age;
private Date birthday;
private String gender;
}
public class PersonDTO {
private String userName;
private Integer age;
private Date birthday;
private Gender gender;
}
我们演示下如何使用MapStruct进行bean映射。
想要使用MapStruct,首先需要依赖他的相关的jar包,使用maven依赖方式如下:
...
<properties>
<org.mapstruct.version>1.3.1.Finalorg.mapstruct.version>
properties>
...
<dependencies>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstructartifactId>
<version>${org.mapstruct.version}version>
dependency>
dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>${org.mapstruct.version}version>
path>
annotationProcessorPaths>
configuration>
plugin>
plugins>
build>
因为MapStruct需要在编译器生成转换代码,所以需要在maven-compiler-plugin插件中配置上对mapstruct-processor的引用。这部分在后文会再次介绍。
之后,我们需要定义一个做映射的接口,主要代码如下:
@Mapper
interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mappings(@Mapping(source = "name", target = "userName"))
PersonDTO do2dto(PersonDO person);
}
使用注解@Mapper定义一个Converter接口,在其中定义一个do2dto方法,方法的入参类型是PersonDO,出参类型是PersonDTO,这个方法就用于将PersonDO转成PersonDTO。
测试代码如下:
public static void main(String[] args) {
PersonDO personDO = new PersonDO();
personDO.setName("Hollis");
personDO.setAge(26);
personDO.setBirthday(new Date());
personDO.setId(1);
personDO.setGender(Gender.MALE.name());
PersonDTO personDTO = PersonConverter.INSTANCE.do2dto(personDO);
System.out.println(personDTO);
}
输出结果:
PersonDTO{userName='Hollis', age=26, birthday=Sat Aug 08 19:00:44 CST 2020, gender=MALE}
可以看到,我们使用MapStruct完美的将PersonDO转成了PersonDTO。
上面的代码可以看出,MapStruct的用法比较简单,主要依赖@Mapper注解。
但是我们知道,大多数情况下,我们需要互相转换的两个类之间的属性名称、类型等并不完全一致,还有些情况我们并不想直接做映射,那么该如何处理呢?
其实MapStruct在这方面也是做的很好的。
名字不一致如何映射
@Mapping(source = "name", target = "userName")
可以自动映射的类型
基本类型及其他们对应的包装类型。 基本类型的包装类型和String类型之间 String类型和枚举类型之间
自定义常量
@Mapping(source = "name", constant = "hollis")
类型不一致的如何映射
public class PersonDO {
private String name;
private String address;
}
public class PersonDTO {
private String userName;
private HomeAddress address;
}
@Mapper
interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mapping(source = "userName", target = "name")
@Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")
PersonDO dto2do(PersonDTO dto2do);
default String homeAddressToString(HomeAddress address){
return JSON.toJSONString(address);
}
}
default方法:Java 8 引入的新的语言特性,用关键字default来标注,被default所标注的方法,需要提供实现,而子类可以选择实现或者不实现该方法
@Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")
@Mapping(target = "birthday",dateFormat = "yyyy-MM-dd HH:mm:ss")
前面说了这么多MapStruct的用法,可以看出MapStruct的使用还是比较简单的,并且字段映射上面的功能很强大,那么他的性能到底怎么样呢?
参考《为什么阿里巴巴禁止使用Apache Beanutils进行属性的copy?》中的示例,我们对MapStruct进行性能测试。
分别执行1000、10000、100000、1000000次映射的耗时分别为:0ms、1ms、3ms、6ms。
可以看到,MapStruct的耗时相比较于其他几款工具来说是非常短的。
那么,为什么MapStruct的性能可以这么好呢?
其实,MapStruct和其他几类框架最大的区别就是:与其他映射框架相比,MapStruct在编译时生成bean映射,这确保了高性能,可以提前将问题反馈出来,也使得开发人员可以彻底的错误检查。
还记得前面我们在引入MapStruct的依赖的时候,特别在maven-compiler-plugin中增加了mapstruct-processor的支持吗?
并且我们在代码中使用了很多MapStruct提供的注解,这使得在编译期,MapStruct就可以直接生成bean映射的代码,相当于代替我们写了很多setter和getter。
如我们在代码中定义了以下一个Mapper:
@Mapper
interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mapping(source = "userName", target = "name")
@Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")
@Mapping(target = "birthday",dateFormat = "yyyy-MM-dd HH:mm:ss")
PersonDO dto2do(PersonDTO dto2do);
default String homeAddressToString(HomeAddress address){
return JSON.toJSONString(address);
}
}
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-08-09T12:58:41+0800",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)"
)
class PersonConverterImpl implements PersonConverter {
@Override
public PersonDO dto2do(PersonDTO dto2do) {
if ( dto2do == null ) {
return null;
}
PersonDO personDO = new PersonDO();
personDO.setName( dto2do.getUserName() );
if ( dto2do.getAge() != null ) {
personDO.setAge( dto2do.getAge() );
}
if ( dto2do.getGender() != null ) {
personDO.setGender( dto2do.getGender().name() );
}
personDO.setAddress( homeAddressToString(dto2do.getAddress()) );
return personDO;
}
}
本文介绍了一款Java中的字段映射工具类,MapStruct,他的用法比较简单,并且功能非常完善,可以应付各种情况的字段映射。
并且因为他是编译期就会生成真正的映射代码,使得运行期的性能得到了大大的提升。