添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

MapStruct 类似于我们熟悉的 BeanUtils, 是一个 Bean 的转换框架。

它与 BeanUtils 最大的不同之处在于,其并不是在程序运行过程中通过反射进行字段复制的,而是在编译期生成用于字段复制的代码(类似于 Lombok 生成 get() 和 set() 方法),这种特性使得该框架在运行时相比于 BeanUtils 有很大的性能提升。

Maven

由于 MapStruct 和 Lombok 都会在编译期生成代码,如果配置不当,则会产生冲突,因此在工程中同时使用这两个包时,应该按照以下方案导入:

1、当 POM 中不包含 Lombok 时:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.2.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.2.Final</version>
</dependency>

2、当 POM 中包含 Lombok 且不包含 <annotationProcessorPaths> 时:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.2.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.2.Final</version>
</dependency>

注意:引入时,mapstruct-processor 必须 lombok 后面。

3、当 POM 中包含 Lombok 且包含 <annotationProcessorPaths> 时:

<properties>
    <org.mapstruct.version>1.5.2.Final</org.mapstruct.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>1.18.24</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>
                    <properties>
                        <org.mapstruct.version>1.5.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>
                                            <groupId>org.projectlombok</groupId>
                                            <artifactId>lombok</artifactId>
                                            <version>1.18.24</version>
                                        </path>
                                            <groupId>org.mapstruct</groupId>
                                            <artifactId>mapstruct-processor</artifactId>
                                            <version>${org.mapstruct.version}</version>
                                        </path>
                                        <!-- other annotation processors -->
                                    </annotationProcessorPaths>
                                </configuration>
                            </plugin>
                        </plugins>
                    </build>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                    <!-- other annotation processors -->
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

Idea Plugin

搜索 MapStruct Support 安装即可,可以在使用 MapStruct 时获得更加丰富代码提示。

1、字段完全一致

待转换的类:

@Data
@Builder
public class Source {
    private Long id;
    private Long age;
    private String userNick;

转换目标类:

@Data
public class Target {
    private Long id;
    private Long age;
    private String userNick;
// 注意:Mapper是Mapstruct的注解。
@Mapper
public abstract class Converter {
    public static Converter INSTANT = Mappers.getMapper(Converter.class);
    public abstract Target convert(Source source);

使用示例:

final Source source = Source.builder()
        .id(1L)
        .age(18L)
        .userNick("Nick")
        .build();
final Target target = Converter.INSTANT.convert(source);
System.out.println(target);

2、对应的字段名不一致、类型不一致

待转换的类:

@Data
@Builder
public class Source {
    private Long id;
    private Long age;
    private String userNick;

转换目标类:

@Data
public class Target {
    private Long id;
    private Integer age;
    private String nick;

转换目标类修改了 age 字段的类型,和 userNick 字段的名字,这两个类的字段仍然是一一对应的。

@Mapper public abstract class Converter { public static Converter INSTANT = Mappers.getMapper(Converter.class); // 字段类型映射修改 @Mapping(source = "age", target = "age", resultType = Integer.class) // 字段名映射修改 @Mapping(source = "userNick", target = "nick") public abstract Target convert(Source source);

使用示例:

final Source source = Source.builder()
        .id(1L)
        .age(18L)
        .userNick("Nick")
        .build();
final Target target = Converter.INSTANT.convert(source);
System.out.println(target);

3、一对多字段互转

在业务代码中,常出现需要将一个类中的一些字段转换为另一个类的 JSON 字段的情况,以下是一个简单的例子:

互相转换的类:

  • VO:前端渲染内容。
  • @Data
    @Builder
    public class VO {
        private Long id;
        private Long age;
        private String userNick;
     
  • DTO:传输内容,其中仅包含 id,其余字段均存放在 extra 字段中。
  • @Data
    public class DTO {
        private Long id;
        private String extra;
    

    1)多个字段转换为一个字段

  • 常用于将多个字段转为 JSON 字段,在以下示例中,为了避免引入第三方包(如 FastJson),仅使用字符串拼接两个字段,Json 方式同理。
  • @Mapper
    public abstract class Converter {
        public static Converter INSTANT = Mappers.getMapper(Converter.class);
        @Mapping(target = "extra", source = "vo", qualifiedByName = "convertToExtra")
        public abstract DTO convert(VO vo);
        @Named("convertToExtra")
        public String convertToExtra(VO vo) {
            return String.format("%s,%s", vo.getAge(), vo.getUserNick());
    

    将多个字段转换为一个字段,需要以下几个步骤:

  • 创建自定义转换方法(本例为 convertToExtra()):
  • 方法入参类型为被转换的类(本例为 VO),出参为转换好的字段(本例为 extra);
  • 为方法加上@Named 注解,并自定义该方法在 mapStruct 中的名字(本例中为 convertToExtra)。
  • 在转换方法上增加 Mapping 注解,其中:
  • source 字段必须与转换方法入参名字相同(本例中均为 vo);
  • target 字段为目标字段(本例中为 extra);
  • qualifiedByName 字段为上述自定义的方法名字。
  • 2)将一个字段转换为多个字段

  • 该方法常用于从 JSON 字段中取出数据。原理与上述方法类似,定义两个自定义转换方法,用于转换 extra 字段。
  • @Mapper
    public abstract class Converter {
        public static Converter INSTANT = Mappers.getMapper(Converter.class);
        @Mapping(target = "age", source = "extra", qualifiedByName = "extractAge")
        @Mapping(target = "userNick", source = "extra", qualifiedByName = "extractUserNick")
        public abstract VO convertToVO(DTO dto);
        @Named("extractAge")
        public Long extractAge(String extra) {
            // 从extra中提取第一个值
            return Long.valueOf(extra.split(",")[0]);
        @Named("extractUserNick")
        public String extractUserNick(String extra) {
            // 从extra中提取第二个值
            return extra.split(",")[1];
    

    使用示例:

    final VO vo = VO.builder()
            .id(1L)
            .age(18L)
            .userNick("Nick")
            .build();
    // 转为DTO
    final DTO dto = Converter.INSTANT.convertToDTO(vo);
    System.out.println(dto);
    // 转回VO
    final VO newVo = Converter.INSTANT.convertToVO(dto);
    System.out.println(newVo);

    4、为转换加缓存

    在上述的两个方法(extractAge 和 extractUserNick)中,进行了重复的 String.split() 操作,如果该操作更加复杂(如从 JSON 串中提取内容),则会造成资源的浪费。

    为此,可以给当前的 converter 加一个缓存字段 extraFieldBufferLocal,如下例所示。在例子中,每次解析 extra 字段前,先判断 buffer 是否存在,如果存在则使用缓存内容。

    注:Mapstruct 中使用 xxx.INSTANT 获得的转换器是单例的,因此,如果要在多线程环境中转换时加入缓存,其缓存必须声明为 ThreadLocal 类型。

    @Mapper
    public abstract class Converter { 
        public static Converter INSTANT = Mappers.getMapper(Converter.class);
         * extra字段解析后的buffer,避免多次重复解析
        private final ThreadLocal<String[]> extraFieldBufferLocal = new ThreadLocal<>();
        @Mapping(target = "age", source = "extra", qualifiedByName = "extractAge")
        @Mapping(target = "userNick", source = "extra", qualifiedByName = "extractUserNick")
        public abstract VO convertToVO(DTO dto);
        @Named("extractAge")
        public Long extractAge(String extra) {
            if (extraFieldBufferLocal.get() == null) {
                extraFieldBufferLocal.set(extractExtraField(extra));
            return Long.valueOf(extraFieldBufferLocal.get()[0]);
        @Named("extractUserNick")
        public String extractUserNick(String extra) {
            if (extraFieldBufferLocal.get() == null) {
                extraFieldBufferLocal.set(extractExtraField(extra));
            return extraFieldBufferLocal.get()[1];
         * 提取extra字段
         * @param extra extra字段
         * @return extra字段的提取结果
        public String[] extractExtraField(final String extra) {
            return extra.split(",");
    

    5、子类字段互转

    常用于平铺类和嵌套类之间的转换,例如,前端需要将类中的所有字段打平,就可以参考以下示例代码。

    互相转换的类:

    @Data
    @Builder
    public class VO {
        private Long id;
        private Date gmtCreate;
        private Long age;
        private String userNick;
    
    @Data
    public class DTO {
        private Long id;
        private Date gmtCreate;
        private Config config;
        @Data
        public static class Config{
            private String age;
            private String userNick;
    

    在 DTO 中,VO 的 age 和 userNick 字段被放到了子类 Config 中。此时也可以使用上一节展示的自定义转换函数法进行转换,不过 MapStruct 提供了一种更加直观简单的转换方法:

    @Mapper public abstract class Converter { public static Converter INSTANT = Mappers.getMapper(Converter.class); @Mapping(target = "config.age", source = "age") @Mapping(target = "config.userNick", source = "userNick") abstract DTO convertToDTO(VO source); @Mapping(target = "age", source = "config.age") @Mapping(target = "userNick", source = "config.userNick") abstract VO convertToVO(DTO dto);

    使用示例:

    final VO vo = VO.builder() .id(1L) .age(10L) .gmtCreate(new Date()) .userNick("nick") .build(); final DTO dto = Converter.INSTANT.convertToDTO(vo); System.out.println(dto); final VO newVo = Converter.INSTANT.convertToVO(dto); System.out.println(newVo);

    6、利用 Spring 进行依赖注入

    本文以上示例代码中,都是使用 Converter.INSTANT 来获得 Convert 实例,这在业务代码中可能显得有些突兀,而 MapStruct 提供了依赖注入的机制,让我们能够在 Spring 的环境下,更优雅的获得 Converter,以下是一个例子:

    @Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
    public abstract class Converter {
        public abstract Target convert(Source source);
    

    使用示例:

    @Controller
    public class MainController {
        @Resource
        private Converter convert;
        @GetMapping("/")
        @ResponseBody
        public boolean test() {
            final Source source = Source.builder()
                    .id(1L)
                    .age(18L)
                    .userNick("nick")
                    .build();
            final Target result = convert.convert(source);
            System.out.println(result);
            return true;