目录
一、前言
在互联网企业中,随着业务越来越繁琐,导致系统架构越来越复杂,很多企业都使用DDD对系统进行拆分。DDD通常分很多层次,每个层次都有相应的逻辑,在不同层次之间进行调用时,难免需要对实体类进行转换,例如将UserDo转化成UserDto。目前业界有很多种对象映射工具,例如:
- json方式。将UserDo转化成json字符串后,再通过fastjson的JSON.parseObject方法将json字符串转化成UserDto类。
- BeanUtils.copyProperties()。BeanUtils.copyProperties()比较有名的有Apache的BeanUtils和Spring的BeanUtils。虽然两者性能上有所差异,但是都是通过反射实现的,总体性能还是欠佳。
- BeanCopier。BeanCopier通过字节码方式转换成性能最好的get和set方式,会动态生成一个被代理的类的子类,总体性能比BeanUtils好。
- 其他编译器转化工具。还有很多在编译期间进行转化的工具,例如MapStruct,Selma,Orika等。
本文主要介绍mapstruct的常用方式。mapstruct是一个实现JSR269的bean映射工具。只需定义一个 mapper接口,该接口声明添加@Mapper注解,然后再mapper接口中定义一个转化方法。mapstruct就能在编译期间将一个实体类的属性值映射到另一个实体类中。本文只梳理mapstruct的一些常见用法,具体原理可以参考mapstruct原理解析。
二、mapstruct使用前的准备
mapstruct使用前需要在项目中引入对应的依赖。
1、maven方式
如果是maven,则使用下列方式引入。
<properties>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
2、Gradle方式
如果Gradle版本>= 4.6,需要添加下列代码到build.gradle中。
dependencies {
...
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
如果Gradle版本 <4.6,需要引入下列代码到build.gradle中。
plugins {
...
id 'net.ltgt.apt' version '0.21'
}
dependencies {
...
compile 'org.mapstruct:mapstruct:1.4.2.Final'
apt 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
3、 Apache Ant方式
如果使用Apache Ant,则需要在build.xml中引入下列代码。
...
<javac
srcdir="src/main/java"
destdir="target/classes"
classpath="path/to/mapstruct-1.4.2.Final.jar">
<compilerarg line="-processorpath path/to/mapstruct-processor-1.4.2.Final.jar"/>
<compilerarg line="-s target/generated-sources"/>
</javac>
...
三、mapstruct使用方式梳理
mapstruct使用方式比较多,还可以支持比较复杂的操作。下面依次列举常见的使用方式。
1、最简单的映射
最简单的映射就是两个实体类的属性名和属性类型完全一一对应,这种方式最简单。为了直观起见,使用两个实体类进行说明。
public class UserDto {
private int id;
private String name;
}
public class UserDO {
private int id;
private String name;
}
为了实现UserDto和UserDO之间实现映射,需要定义一个接口:
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDto toDto(UserDo userDo);
}
上述UserMapper接口在编译期间会被mapstruct创建一个实现类UserMapperImpl,然后实现toDto方法。实现类如下代码所示:
public class UserMapperImpl implements UserMapper {
@Override
public UserDto toDto(UserDo userDo) {
if (userDo == null) {
return null;
}
UserDto userDto = new UserDto();
if ( userDo.getId() != null ) {
userDto.setId( userDo.getId() );
}
if ( userDo.getName() != null ) {
userDto.setUserName( userDo.getName() );
}
return userDto
}
2、不同属性名映射
很多场景两个映射实体直接的属性名可能不同,但是却需要将他们的值进行映射,老规矩还是先列出两个映射实体,再介绍使用方式。
public class UserDto {
private int id;
private String name;
}
public class UserDo {
private int id;
private String userName;
}
上述UserDto的name和UserDo的userName两个属性名不同。为了让mapstruct实现他们之间的映射,需要在方法上显示的标记。具体实现如下代码所示:
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "userName", target = "name")
UserDto toDto(UserDo userDo);
}
@Mapping注解能够将UserDo中的userName对应到UserDto中的name字段。
如果UserDo和UserDto有多个属性名不同,则需要使用@Mappings注解包裹@Mapping,假设UserDo和UserDto代码如下:
public class UserDto {
private int id;
private String name;
private int age;
}
public class UserDo {
private int id;
private String userName;
private int userAge;
}
需要在toDto方法上加上@Mappings注解。
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mappings({
@Mapping(source = "userName", target = "name"),
@Mapping(source = "userAge", target = "age")
})
UserDto toDto(UserDo userDo);
}
3、不同类型映射
1)、日期格式转化
mapstruct支持日期类型与其他类型的转化。如将LocalDate格式映射成Sting,可以使用如下方式:
public class UserDto {
private int id;
private String name;
private int age;
private String birthday;
}
public class UserDo {
private int id;
private String name;
private int age;
private LocalDate birthday;
}
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "birthday", target = "birthday", dateFormat = "dd/MMM/yyyy")
UserDto toDto(UserDo userDo);
}
上述的@Mapping里添加了dateFormat,mapstruct使用下列代码将birthday转化成String类型。
DateTimeFormatter.ofPattern("dd/MMM/yyyy") .format(UserDo.getBirthday())
2)、数字格式转化
数字格式的转化,可以使用numberFormat进行转换。代码如下所示:
public class UserDto {
private int id;
private String name;
private int age;
private String wealth;
}
public class UserDo {
private int id;
private String name;
private int age;
private int wealth;
}
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "wealth", target = "wealth", numberFormat = "$#.00")
UserDto toDto(UserDo userDo);
}
4、List映射
mapstruct支持List的映射,使用List转化是需要有单个实体之间的转化方法,具体代码如下:
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "wealth", target = "wealth", numberFormat = "$#.00")
UserDto toDto(UserDo userDo);
List<UserDto> toDtos(List<UserDo> userDoList);
}
注意,假如UserDto中有个字段在UserDo中不存在,在生成代码时会报错。因此需要在toDto中添加@Mapping注解,将不存在的字段进行特殊映射或者直接忽略掉。代码如下所示:
public class UserDto {
private int id;
private String name;
private int age;
private String wealth;
}
public class UserDo {
private int id;
private String name;
private int age;
}
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(target = "wealth", ignore = true)
UserDto toDto(UserDo userDo);
List<UserDto> toDtos(List<UserDo> userDoList);
}
5、Map和Set映射
Map和Set的映射与List类似,具体示例如下:
public class UserDto {
private int id;
private String name;
private int age;
private String wealth;
}
public class UserDo {
private int id;
private String name;
private int age;
}
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(target = "wealth", ignore = true)
UserDto toDto(UserDo userDo);
List<UserDto> toDtos(List<UserDo> userDoList);
Set<UserDto> toDtoSet(Set<UserDo> userDoSet);
Map<Long, UserDto> toDtoMap(Map<Long, UserDo> userDoMap);
}
6、枚举映射
枚举映射与实体类的字段映射类似,mapstruct会根据相同的名称自动进行映射。不过如果字段名称不同,则需要使用@ValueMapping注解。代码示例如下:
public enum ExampleMappingEnum {
ENUM1,
ENUM2,
ENUM_MAPPING3,
ENUM_MAPPING4
}
public enum ExampleEnum {
ENUM1,
ENUM2,
ENUM3
}
@Mapper
public interface ExampleEnumMapper {
ExampleEnumMapper INSTANCE = Mappers.getMapper(ExampleEnumMapper.class);
@ValueMappings({
@ValueMapping(source = "ENUM_MAPPING3", target = "ENUM3"),
@ValueMapping(source = "ENUM_MAPPING4", target = "ENUM3")
})
ExampleEnum map(ExampleMappingEnum exampleMappingEnum);
}
上述代码的ExampleMappingEnum比ExampleEnum多一个值,并且枚举值也不同,需要通过@ValueMapping进行转化,mapstruct编译后的代码如下:
public class ExampleEnumMapperImpl implements ExampleEnumMapper {
@Override
public ExampleEnum map(ExampleMappingEnum exampleMappingEnum) {
if (exampleMappingEnum == null) {
return null;
}
ExampleEnum exampleEnum;
switch (exampleMappingEnum) {
case ENUM_MAPPING3: exampleEnum = ExampleEnum.ENUM3;
break;
case ENUM_MAPPING4: exampleEnum = ExampleEnum.ENUM3;
break;
case ENUM1: exampleEnum = ExampleEnum.ENUM1;
break;
case ENUM2: exampleEnum = ExampleEnum.ENUM2;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + exampleMappingEnum );
}
return exampleEnum;
}
}
假如不想像上述那样指定某几个枚举值映射到特定的枚举值,也可以直接使用MappingConstants进行默认映射。
@Mapper
public interface ExampleEnumMapper {
ExampleEnumMapper INSTANCE = Mappers.getMapper(ExampleEnumMapper.class);
@@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "ENUM3")
ExampleEnum map(ExampleMappingEnum exampleMappingEnum);
}
通过MappingConstants,mapstruct会将所有未映射的值映射到ENUM3中。
四、总结
本文主要讲解了mapstruct比较常见的使用方式。在最开始简单比较了下目前行业中的一些映射工具,然后介绍了下mapstruct的使用前准备工作,最后梳理了mapstruct一些比较常规的使用方式。当然mapstruct还有很多高级的用法,限于篇幅,就暂时不展开,可以参考mapstruct官网。
参考
Installation – MapStructhttps://mapstruct.org/documentation/installation/