MapStruct对象映射工具的使用
官网地址
介绍
MapStruct是一个对象转换映射工具,用来不同类型责则对象间的转换,如DO、DTO、VO之间的互转,我们来看看官网的介绍。
MapStruct is a Java annotation processor for the generation of type-safe and performant mappers for Java bean classes. It saves you from writing mapping code by hand, which is a tedious and error-prone task. The generator comes with sensible defaults and many built-in type conversions, but it steps out of your way when it comes to configuring or implementing special behavior. 简单翻译一下:MapStruct 是一个 Java 注释处理器,用于为 Java bean 类生成类型安全和高性能的映射器。它使您免于手动编写映射代码,这是一项繁琐且容易出错的工作。生成器带有合理的默认值和许多内置的类型转换,但在配置或实现特殊行为时它可能会比较繁琐。
为什么要用MapStruct
对象间的字段转换映射,已经用许多现成可用的方案,用的比较多的应该是Spring中自带的BeanUtils,用起来也十分方便,但是美中不足的是其底层原理是使用了反射去实现字段间的映射,性能方面就很一般了。而MapStruct在这一点上,使用getter、setter实现,性能远远高于BeanUtils,以下是官网所描述的优点:
- Fast execution by using plain method invocations instead of reflection
- Compile-time type safety. Only objects and attributes mapping to each other can be mapped, so there’s no accidental mapping of an order entity into a customer DTO, etc.
- Self-contained code—no runtime dependencies
- Clear error reports at build time if:
- mappings are incomplete (not all target properties are mapped)
- mappings are incorrect (cannot find a proper mapping method or type conversion)
- Easily debuggable mapping code (or editable by hand—e.g. in case of a bug in the generator) 简单翻译一下:
- 通过使用普通方法调用而不是反射来实现快速执行
- 编译时类型安全。只能映射相互映射的对象和属性,因此不会出现将订单实体类意外映射到客户 DTO类 等。
- 自包含代码——无运行时依赖
- 如果出现以下情况,编译时可以被发现:
- 映射不完整(并非所有目标属性都已映射)
- 映射不正确(找不到合适的映射方法或类型转换)
- 易于调试的映射代码(或可手动编辑——例如,在生成器中出现错误的情况下)
使用
引入相关依赖
Maven
...
<properties>
<org.mapstruct.version>1.5.3.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>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
...
Gradle
plugins {
...
id "com.diffplug.eclipse.apt" version "3.26.0" // Only for Eclipse
}
dependencies {
...
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' // if you are using mapstruct in test code
}
...
MapStruct整合Lombok
MapStruce和Lombok的实现原理类似,都是在编译期间对原有代码进行增强,生成对应代码,所以MapStruce和Lombok同时使用时,需注意一下pom文件的配置。 MapStruce官网关于Lombok的说明 可以参考以下pom配置文件:
<properties>
<maven.compiler.source>13</maven.compiler.source>
<maven.compiler.target>13</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
<org.projectlombok.version>1.18.24</org.projectlombok.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>13</source>
<target>13</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<!--lombok版本大于1.18.16 需配置以下path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
开始使用
创建需要映射的实体类
首先,我们创建一个用户实体类DO,和对应的DTO,字段先保持一致。
@Data
@Builder
public class UserDO {
private String name;
private Integer age;
private String gender;
private String phoneNumber;
}
@Data
@Builder
public class UserDTO {
private String name;
private Integer age;
private String gender;
private String phoneNumber;
}
创建Mapper映射
创建一个接口,并使用MapStruct的@Mapper注解,MapStruct就可以生成对应的映射代码,实现对象间的映射转换。
创建通用泛型映射Mapper
我们可以将原实体类和目标实体类用泛型抽象出来,写一个通用的映射Mapper。
public interface BaseMapStruct<SOURCE,TARGET>{
TARGET toTarget(SOURCE source);
SOURCE toSource(TARGET target);
List<SOURCE> toSourceList(List<TARGET> targetList);
List<TARGET> toTargetList(List<SOURCE> sourceList);
}
创建具体的Mapper
有了通用的Mapper,具体类的Mapper只需要创建对应的接口并实现BaseMapStruct即可。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) //使用Spring容器托管类
public interface UserMapStruct extends BaseMapStruct<UserDO, UserDTO> {
//特殊的字段转换处理 可以在具体的映射器上实现
}
测试基本映射
@SpringBootTest
@Slf4j
public class MapStructApplicationTest {
@Resource
UserMapStruct userMapStruct;
@Test
public void testMapStruct(){
UserDO userDO = UserDO.builder().name("test").age(18).phoneNumber("12345678").gender("man").build();
log.info(userMapStruct.toTarget(userDO).toString());
}
}
执行结果:
2023-01-14 22:26:06.265 INFO 8776 --- [ main] c.t.learning.MapStructApplicationTest : UserDTO(name=test, age=18, gender=man, phoneNumber=12345678)
查看MapStruct自动生成的映射实现类
MapStruct通过解析注解,在编译期间生成对应映射器的实现类,我们打开target文件夹中的对应路径,看一下自动生成的内容:
@Component
public class UserMapStructImpl implements UserMapStruct {
public UserMapStructImpl() {
}
public UserDTO toTarget(UserDO source) {
if (source == null) {
return null;
} else {
UserDTO.UserDTOBuilder userDTO = UserDTO.builder();
userDTO.name(source.getName());
userDTO.age(source.getAge());
userDTO.gender(source.getGender());
userDTO.phoneNumber(source.getPhoneNumber());
return userDTO.build();
}
}
public UserDO toSource(UserDTO target) {
if (target == null) {
return null;
} else {
UserDO.UserDOBuilder userDO = UserDO.builder();
userDO.name(target.getName());
userDO.age(target.getAge());
userDO.gender(target.getGender());
userDO.phoneNumber(target.getPhoneNumber());
return userDO.build();
}
}
public List<UserDO> toSourceList(List<UserDTO> targetList) {
if (targetList == null) {
return null;
} else {
List<UserDO> list = new ArrayList(targetList.size());
Iterator var3 = targetList.iterator();
while(var3.hasNext()) {
UserDTO userDTO = (UserDTO)var3.next();
list.add(this.toSource(userDTO));
}
return list;
}
}
public List<UserDTO> toTargetList(List<UserDO> sourceList) {
if (sourceList == null) {
return null;
} else {
List<UserDTO> list = new ArrayList(sourceList.size());
Iterator var3 = sourceList.iterator();
while(var3.hasNext()) {
UserDO userDO = (UserDO)var3.next();
list.add(this.toTarget(userDO));
}
return list;
}
}
}
由于使用类Spring容器托管,MapStruct自动生成了 @Component注解,将Bean注册到容器中,所以我们可以使用 @Resoure直接进行注入并使用。由于继承了泛型的Mapper,所以也生成了泛型Mapper中所定义的通用转换方法。值得注意的是,如果转换的对象使用了Lombok的Builder注解,MapStruct会使用对应的Builder去创建对象,反之则直接使用new的方式创建。
自定义字段间映射
字段名不同
在真实的业务场景中,往往会出现相互转换的实体间字段名称不一样的情况,MapStruct也提供了对应注解 @Mapping来解决这个问题。 比如,UserDO的字段phoneNumber对应的是UserDTO的phoneNum字段,我们可以使用 @Mapping将其映射。
@Data
@Builder
public class UserDO {
private String name;
private Integer age;
private String gender;
private String phoneNumber;
}
@Data
@Builder
public class UserDTO {
private String name;
private Integer age;
private String gender;
private String phoneNum;
}
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserMapStruct extends BaseMapStruct<UserDO, UserDTO> {
@Override
@Mapping(source = "phoneNumber",target = "phoneNum")
UserDTO toTarget(UserDO userDO);
@Override
@Mapping(source = "phoneNum",target = "phoneNumber")
UserDO toSource(UserDTO userDTO);
@Override
@Mapping(source = "phoneNum",target = "phoneNumber")
List<UserDO> toSourceList(List<UserDTO> userDTOS);
@Override
@Mapping(source = "phoneNumber",target = "phoneNum")
List<UserDTO> toTargetList(List<UserDO> userDOS);
}
运行结果
@SpringBootTest
@Slf4j
public class MapStructApplicationTest {
@Resource
UserMapStruct userMapStruct;
@Test
public void testMapStruct(){
UserDO userDO = UserDO.builder().name("test").age(18).phoneNumber("12345678").gender("man").build();
UserDTO userDTO = userMapStruct.toTarget(userDO);
log.info(userDTO.toString());
log.info(userMapStruct.toSource(userDTO).toString());
}
}
2023-01-15 21:47:41.370 INFO 65373 --- [ main] c.t.learning.MapStructApplicationTest : UserDTO(name=test, age=18, gender=man, phoneNum=12345678)
2023-01-15 21:47:41.371 INFO 65373 --- [ main] c.t.learning.MapStructApplicationTest : UserDO(name=test, age=18, gender=man, phoneNumber=12345678)
多个数据源
在某些场景下,一个DTO类的数据源往往不只来源于一个DO类。比如一个UserDTO类可能还要有其权限、用户组,头像等信息字段,这些字段的值不一定都来自于UserDO类,可能有对应的RuleDO类等。这种情况下,使用 @Mapping注解也可以解决。 首先我们新建RuleDO类。
@Data
@Builder
public class RoleDO {
/**
* 名称
*/
String name;
/**
* 编码
*/
String code;
}
UserDTO新增roleName、roleCode字段。
@Data
@Builder
public class UserDTO {
private String name;
private Integer age;
private String gender;
private String phoneNum;
private String roleName;
private String roleCode;
}
映射类中添加对应映射注解,值得注意的是,如果两个数据源类中都存在目标类中的字段,也需要使用 @Mapping标记需要使用其中的哪一个。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserMapStruct extends BaseMapStruct<UserDO, UserDTO> {
@Override
@Mapping(source = "phoneNumber",target = "phoneNum")
UserDTO toTarget(UserDO userDO);
@Override
@Mapping(source = "phoneNum",target = "phoneNumber")
UserDO toSource(UserDTO userDTO);
@Override
@Mapping(source = "phoneNum",target = "phoneNumber")
List<UserDO> toSourceList(List<UserDTO> userDTOS);
@Override
@Mapping(source = "phoneNumber",target = "phoneNum")
List<UserDTO> toTargetList(List<UserDO> userDOS);
@Mappings(value = {@Mapping(source = "userDO.phoneNumber",target = "phoneNum"),
@Mapping(source = "userDO.name",target = "name"), //由于UserDO和RoleDO中都存在name字段,所以需使用注解表明使用哪一个
@Mapping(source = "roleDO.name",target = "roleName"),
@Mapping(source = "roleDO.code",target = "roleCode")
})
UserDTO toUserDTO(UserDO userDO, RoleDO roleDO);
}
运行结果
@SpringBootTest
@Slf4j
public class MapStructApplicationTest {
@Resource
UserMapStruct userMapStruct;
@Test
public void testMapStruct(){
UserDO userDO = UserDO.builder().name("test").age(18).phoneNumber("12345678").gender("man").build();
RoleDO roleDO = RoleDO.builder().name("管理员").code("admin").build();
UserDTO userDTO = userMapStruct.toUserDTO(userDO,roleDO);
log.info(userDTO.toString());
}
}
2023-01-15 22:47:55.156 INFO 79977 --- [ main] c.t.learning.MapStructApplicationTest : UserDTO(name=test, age=18, gender=man, phoneNum=12345678, roleName=管理员, roleCode=admin)
子对象与子对象间的映射
子对象的映射
对象中如果存在子对象,那子对象中的字段也是可以平铺映射到一个类中的,也是使用 @Mapping注解去指定映射。 比如现在userDO类中组合了RoleDO类。
@Data
@Builder
public class UserDO {
private String name;
private Integer age;
private String gender;
private String phoneNumber;
private RoleDO roleDO;
}
修改映射规则。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserMapStruct extends BaseMapStruct<UserDO, UserDTO> {
@Override
@Mappings(value = {@Mapping(source = "userDO.phoneNumber",target = "phoneNum"),
@Mapping(source = "userDO.name",target = "name"),
@Mapping(source = "userDO.roleDO.name",target = "roleName"),
@Mapping(source = "userDO.roleDO.code",target = "roleCode")
})
UserDTO toTarget(UserDO userDO);
}
运行结果
@SpringBootTest
@Slf4j
public class MapStructApplicationTest {
@Resource
UserMapStruct userMapStruct;
@Test
public void testMapStruct(){
RoleDO roleDO = RoleDO.builder().name("管理员").code("admin").build();
UserDO userDO = UserDO.builder().name("test").age(18).phoneNumber("12345678").gender("man").roleDO(roleDO).build();
UserDTO userDTO = userMapStruct.toTarget(userDO);
log.info(userDTO.toString());
}
}
2023-01-16 22:00:39.916 INFO 52252 --- [ main] c.t.learning.MapStructApplicationTest : UserDTO(name=test, age=18, gender=man, phoneNum=12345678, roleName=管理员, roleCode=admin)
子对象的映射
如果源对象与目标对象中各自有对应的子对象,比如UserDO和UserDTO中各自有各自的RoleDO和RoleDTO类,这种情况也是可以成功进行映射转换的,当然,也是通过配置 @Mapping 实现的。 首先创建对应的Role类。
@Data
@Builder
public class RoleDTO {
/**
* 名称
*/
private String name;
/**
* 编码
*/
private String code;
}
@Data
@Builder
public class RoleDO {
/**
* 名称
*/
private String name;
/**
* 编码
*/
private String code;
}
然后创建子对象的映射器。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface RoleMapStruct extends BaseMapStruct<RoleDO, RoleDTO>{
//可以在这里实现特殊处理
}
然后在User的映射器中 @Mapping使用uses,将RoleMapStruct引入,User映射器中就可以使用引入的RoleMapStruct进行子对象的映射了。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = RoleMapStruct.class)
public interface UserMapStruct extends BaseMapStruct<UserDO, UserDTO> {
@Override
@Mapping(source = "roleDO",target = "roleDTO") //字段名不一致,手动指定
UserDTO toTarget(UserDO userDO);
}
运行结果
@SpringBootTest
@Slf4j
public class MapStructApplicationTest {
@Resource
UserMapStruct userMapStruct;
@Resource
ObjectMapper objectMapper;
@Test
public void testMapStruct() throws JsonProcessingException {
RoleDO roleDO = RoleDO.builder().name("管理员").code("admin").build();
UserDO userDO = UserDO.builder().name("test").age(18).phoneNumber("12345678").gender("man").roleDO(roleDO).build();
UserDTO userDTO = userMapStruct.toTarget(userDO);
log.info(objectMapper.writeValueAsString(userDTO));
}
}
2023-01-16 23:02:27.911 INFO 67249 --- [ main] c.t.learning.MapStructApplicationTest : {"name":"test","age":18,"gender":"man","phoneNumber":"12345678","roleDTO":{"name":"管理员","code":"admin"}}
基本类型的隐式转换和字符串的格式化
有的时候,映射的两个类之间的字段类型并不会完全一致,如果对应字段为基本数据类型和他们的包装类型,则MapStruct会自动进行空值判断并映射赋值。值得注意的是,如果出现数据范围大的类型映射到数据范围较小的类型如从long到int,则会出现精度丢失的问题,我们可以在Mapper注解中设置处理策略,由于向下兼容,默认为ReportingPolicy.IGNORE。
@Data
@Builder
public class UserDO {
private String name;
private Integer age;
private Integer height;
private String gender;
private String phoneNumber;
private Date createDate;
}
@Data
@Builder
public class UserDTO implements Serializable {
private String name;
private int age; //基本类型间自动映射
private String gender;
private String height; //数字字符串格式化
private String phoneNumber;
private String createDate; //日期格式化
}
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserMapStruct extends BaseMapStruct<UserDO, UserDTO> {
@Override
@Mappings(value = {
@Mapping(source = "createDate",dateFormat = "yyyy-MM-dd",target = "createDate"),
@Mapping(source = "height",numberFormat = "#cm",target = "height")})
UserDTO toTarget(UserDO userDO);
}
运行结果
@SpringBootTest
@Slf4j
public class MapStructApplicationTest {
@Resource
UserMapStruct userMapStruct;
@Resource
ObjectMapper objectMapper;
@Test
public void testMapStruct() throws JsonProcessingException {
UserDO userDO = UserDO.builder().name("test").age(18).phoneNumber("12345678").gender("man").createDate(new Date()).height(180).build();
UserDTO userDTO = userMapStruct.toTarget(userDO);
log.info(objectMapper.writeValueAsString(userDTO));
}
}
2023-01-20 22:03:17.990 INFO 99724 --- [ main] c.t.learning.MapStructApplicationTest : {"name":"test","age":18,"gender":"man","height":"180cm","phoneNumber":"12345678","createDate":"2023-01-20"}
自定义映射前和映射后的处理
MapStruct提供了两个注解,分别是AfterMapping,BeforeMapping用来处理映射前和映射后的自定义实现。以用户类为例,我们可以在映射前设置默认身高,在映射后判断是否为管理员。
@Data
@Builder
public class UserDO {
private String name;
private Integer age;
private Integer height;
private String roleName;
private String gender;
private String phoneNumber;
private Date createDate;
}
@Data
@Builder
@AllArgsConstructor
public class UserDTO implements Serializable {
private String name;
private int age;
private String gender;
private String height;
private String phoneNumber;
private String createDate;
private Boolean admin;
private String roleName;
}
值得注意的是,如果使用AfterMapping,BeforeMapping,@Mapper注解需使用在抽象类中,当实体类使用Builder模式时,则@AfterMapping中需传入对应的Builder,或者是将@Mapper注解中的构造器声明关闭(builder = @Builder(disableBuilder = true))。 如下:
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING,builder = @Builder(disableBuilder = true)) //实体类使用构建器模式时,需关闭
public abstract class UserMapper {
@BeforeMapping
protected void setDefHeight(UserDO userDO){
if(ObjectUtils.isEmpty(userDO.getHeight())){
userDO.setHeight(170);
}
}
@AfterMapping
protected void setAdmin(@MappingTarget UserDTO userDTO){
userDTO.setAdmin(userDTO.getRoleName().equals("admin"));
}
public abstract UserDTO toTarget(UserDO userDO);
}
运行结果
@SpringBootTest
@Slf4j
public class MapStructApplicationTest {
@Resource
UserMapper userMapper;
@Resource
ObjectMapper objectMapper;
@Test
public void testMapStruct() throws JsonProcessingException {
UserDO userDO = UserDO.builder().name("test").age(18).phoneNumber("12345678").gender("man")
.createDate(new Date()).roleName("admin").build();
UserDTO userDTO = userMapper.toTarget(userDO);
log.info(objectMapper.writeValueAsString(userDTO));
}
}
2023-01-25 13:13:06.347 INFO 6247 --- [ main] c.t.learning.MapStructApplicationTest : {"name":"test","age":18,"gender":"man","height":"170","phoneNumber":"12345678","createDate":"2023/1/25 下午1:13","admin":true,"roleName":"admin"}
将Map映射到Bean
在某些特殊的情况下,需要从Map中映射到特定的Bean中,MapStruct也提供了对应的支持,通过Mapping注解,同时可以自定义字段名映射规则。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserMapStruct extends BaseMapStruct<UserDO, UserDTO> {
@Mapping(source = "userName",target = "name") //将map中userName为key的值映射到UserDO类中的name字段
UserDO toUserDO(Map<String, String> map);
}
运行结果
@SpringBootTest
@Slf4j
public class MapStructApplicationTest {
@Resource
UserMapStruct userMapStruct;
@Resource
ObjectMapper objectMapper;
@Test
public void testMapToBean() throws JsonProcessingException {
Map userMap = Map.of("userName","testName",
"height","2","roleName","admin");
UserDO userDO = userMapStruct.toUserDO(userMap);
log.info(objectMapper.writeValueAsString(userDO));
}
}
2023-01-27 18:27:54.915 INFO 16729 --- [ main] c.t.learning.MapStructApplicationTest : {"name":"testName","age":null,"height":2,"roleName":"admin","gender":null,"phoneNumber":null,"createDate":null}
简单分析下原理
善于仔细观察的同学应该已经发现了,使用了MapStruct后,在编译后的target目录中,会多出几个Mapper 的实现类。我们可以简单的看一下生成的Class。
@Component
public class UserMapStructImpl implements UserMapStruct {
public UserMapStructImpl() {
}
public UserDO toSource(UserDTO target) {
if (target == null) {
return null;
} else {
UserDO.UserDOBuilder userDO = UserDO.builder();
userDO.name(target.getName());
userDO.age(target.getAge());
if (target.getHeight() != null) {
userDO.height(Integer.parseInt(target.getHeight()));
}
userDO.roleName(target.getRoleName());
userDO.gender(target.getGender());
userDO.phoneNumber(target.getPhoneNumber());
try {
if (target.getCreateDate() != null) {
userDO.createDate((new SimpleDateFormat()).parse(target.getCreateDate()));
}
} catch (ParseException var4) {
throw new RuntimeException(var4);
}
return userDO.build();
}
}
public List<UserDO> toSourceList(List<UserDTO> targetList) {
if (targetList == null) {
return null;
} else {
List<UserDO> list = new ArrayList(targetList.size());
Iterator var3 = targetList.iterator();
while(var3.hasNext()) {
UserDTO userDTO = (UserDTO)var3.next();
list.add(this.toSource(userDTO));
}
return list;
}
}
public List<UserDTO> toTargetList(List<UserDO> sourceList) {
if (sourceList == null) {
return null;
} else {
List<UserDTO> list = new ArrayList(sourceList.size());
Iterator var3 = sourceList.iterator();
while(var3.hasNext()) {
UserDO userDO = (UserDO)var3.next();
list.add(this.toTarget(userDO));
}
return list;
}
}
public UserDTO toTarget(UserDO userDO) {
if (userDO == null) {
return null;
} else {
UserDTO.UserDTOBuilder userDTO = UserDTO.builder();
if (userDO.getCreateDate() != null) {
userDTO.createDate((new SimpleDateFormat("yyyy-MM-dd")).format(userDO.getCreateDate()));
}
if (userDO.getHeight() != null) {
userDTO.height((new DecimalFormat("#cm")).format(userDO.getHeight()));
}
userDTO.name(userDO.getName());
if (userDO.getAge() != null) {
userDTO.age(userDO.getAge());
}
userDTO.gender(userDO.getGender());
userDTO.phoneNumber(userDO.getPhoneNumber());
userDTO.roleName(userDO.getRoleName());
return userDTO.build();
}
}
public UserDO toUserDO(Map<String, String> map) {
if (map == null) {
return null;
} else {
UserDO.UserDOBuilder userDO = UserDO.builder();
if (map.containsKey("userName")) {
userDO.name((String)map.get("userName"));
}
if (map.containsKey("age")) {
userDO.age(Integer.parseInt((String)map.get("age")));
}
if (map.containsKey("height")) {
userDO.height(Integer.parseInt((String)map.get("height")));
}
if (map.containsKey("roleName")) {
userDO.roleName((String)map.get("roleName"));
}
if (map.containsKey("gender")) {
userDO.gender((String)map.get("gender"));
}
if (map.containsKey("phoneNumber")) {
userDO.phoneNumber((String)map.get("phoneNumber"));
}
try {
if (map.containsKey("createDate")) {
userDO.createDate((new SimpleDateFormat()).parse((String)map.get("createDate")));
}
} catch (ParseException var4) {
throw new RuntimeException(var4);
}
return userDO.build();
}
}
}
MapStruct通过扫描解析Mapper注解,在编译的过程中生成对应的实现类的代码,从而实现对象间复杂的映射。问题来了,MapStruct是怎么在编译过程中生成额外的代码?这个就涉及到JSR269所提供的一套API注解处理器(Annotation Processor Tool),简称APT。
注解处理器(Annotation Processor Tool)
APT允许我们在编译期访问和处理注解元数据,修改或创建源文件或类文件,像反射一样访问类、字段、方法和注解等元素。它可以用于减少样板代码,实现代码生成器,检查代码规范等等。要实现一个注解处理器,我们需要继承AbstractProcessor类,重写process方法,并在META-INF/services目录下注册我们的注解处理器。Lombok就是一个利用JSR269规范实现的插件,它可以通过注解来自动生成getter,setter,toString等方法。
总结
和Lombok的原理一样,MapStruct通过注解处理器,在编译的过程中扫描注解,生成对应的对应映射代码,大大方便了对象间的相互映射操作,不仅提高了开发效率,而且与其他实现方式相比,它的转换性能也是比反射要高的。在频繁需要映射对象的场景下尤为明显。可以说是一个优秀的代码工具了。