MapStruct对象映射工具的使用

官网地址

MapStruct官网 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在这一点上,使用gettersetter实现,性能远远高于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提供了两个注解,分别是AfterMappingBeforeMapping用来处理映射前和映射后的自定义实现。以用户类为例,我们可以在映射前设置默认身高,在映射后判断是否为管理员。

@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;
}

值得注意的是,如果使用AfterMappingBeforeMapping,@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通过注解处理器,在编译的过程中扫描注解,生成对应的对应映射代码,大大方便了对象间的相互映射操作,不仅提高了开发效率,而且与其他实现方式相比,它的转换性能也是比反射要高的。在频繁需要映射对象的场景下尤为明显。可以说是一个优秀的代码工具了。