业务代码的救星——Java 对象转换框架 MapStruct 妙用

简介

在业务项目的开发中,咱们常常须要将 Java 对象进行转换,好比从将外部微服务获得的对象转换为本域的业务对象 domain object,将 domain object 转为数据持久层的 data object,将 domain object 转换为 DTO 以便返回给外部调用方等。在转换时大部分属性都是相同的,只有少部分的不一样,若是手工编写转换代码,会很繁琐。这时咱们能够经过一些对象转换框架来更方便的作这件事情。java

这样的对象转换框架有很多,比较有名的有 ModelMapper 和 MapStruct。它们所使用的实现技术不一样,ModelMapper 是基于反射的,经过反射来查找实体对象的字段,并读取或写入值,这样的方式实现原理简单,但性能不好。与 ModelMapper 框架不一样的是,MapStruct 是基于编译阶段代码生成的,生成的转换代码在运行的时候跟通常的代码同样,没有额外的性能损失。本文重点介绍 MapStruct。数据库

业务场景

假设如今有这么个场景,从数据库查询出来了一个 user 对象(包含 id,用户名,密码,手机号,邮箱,角色这些字段)和一个对应的角色对象 role(包含 id,角色名,角色描述这些字段),如今在 controller 须要用到 user 对象的 id,用户名,和角色对象的角色名三个属性。一种方式是直接把两个对象传递到 controller 层,可是这样会多出不少没用的属性。更通用的方式是须要用到的属性封装成一个类(DTO),经过传输这个类的实例来完成数据传输。apache

实现方式之使用传统方式

以下:安全

User.java微信

@AllArgsConstructor
@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private String phoneNum;
    private String email;
    private Role role;
}
复制代码

Role.javaapp

@AllArgsConstructor
@Data
public class Role {
    private Long id;
    private String roleName;
    private String description;
}
复制代码

UserRoleDto.java框架

@Data
public class UserRoleDto {
    /** * 用户id */
    private Long userId;
    /** * 用户名 */
    private String name;
    /** * 角色名 */
    private String roleName;
}
复制代码

MainTest.javadom

测试类,模拟将 user 对象转换成 UserRoleDto 对象maven

public class MainTest {

    User user = null;

    /** * 模拟从数据库中查出 user 对象 */
    @Before
    public void before() {
        Role role  = new Role(2L, "administrator", "超级管理员");
        user  = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);
    }

    /** * 模拟把 user 对象转换成 UserRoleDto 对象 */
    @Test
    public void test1() {
        UserRoleDto userRoleDto = new UserRoleDto();
        userRoleDto.setUserId(user.getId());
        userRoleDto.setName(user.getUsername());
        userRoleDto.setRoleName(user.getRole().getRoleName());
        System.out.println(userRoleDto);
    }
}
复制代码

运行结果 ide

file

上边的代码或许暂时看起来仍是比较简洁的,可是咱们须要注意的一点就是平时业务开发中的对象属性远不是上述代码中简简单单的几个字段,有可能会有数十个字段,同理也会数十个对象须要转换,咱们若是仍是经过 getter、setter 的方式把一个对象属性值复制到另外一个对象中去仍是很是麻烦的,不过不用担忧,今天要介绍给你们的 MapStruct 就是用于解决这种问题的。

实现方式之使用 MapStruct

这里咱们沿用上述代码中的基本对象 User.javaRole.javaUserRoleDto.java。 而后新建一个 UserRoleMapper.java,这个来用来定义 User.javaRole.javaUserRoleDto.java之间属性对应规则。

在这以前咱们须要引入 MapStruct 的 pom 引用:

<dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct-jdk8</artifactId>
      <version>1.3.0.Final</version>
</dependency>
复制代码

UserRoleMapper.java

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/** * @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则 * 在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制 */
@Mapper
public interface UserRoleMapper {

    /** * 获取该类自动生成的实现类的实例 * 接口中的属性都是 public static final 的 * 方法都是public abstract 的 */
    UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);

    /** * 这个方法就是用于实现对象属性复制的方法 * * @Mapping 用来定义属性复制规则 * source 指定源对象属性 * target 指定目标对象属性 * * @param user 这个参数就是源对象,也就是须要被复制的对象 * @return 返回的是目标对象,就是最终的结果对象 */
    @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "username", target = "name"),
            @Mapping(source = "role.roleName", target = "roleName")
    })
    UserRoleDto toUserRoleDto(User user);

}
复制代码

测试一下结果

MainTest.java

/** * 模拟经过MapStruct把user对象转换成UserRoleDto对象 */
    @Test
    public void test2() {
        UserRoleDto userRoleDto = UserRoleMapper.INSTANCES.toUserRoleDto(user);
        System.out.println(userRoleDto);
    }
复制代码

呃,很明显,运行居然报错了,具体异常以下:

file

核心是这一句 :java.lang.ClassNotFoundException: Cannot find implementation for top.zhoudl.mapstruct.UserRoleMapper ,也就是说没有找到 UserRoleMapper 类的实现类。

经过查阅一些资料可得:

MapStruct 是一个能够处理注解的Java编译器插件,能够在命令行中使用,也能够在 IDE 中使用。MapStruc t有一些默认配置,可是也为用户提供了本身进行配置的途径。缺点就是这玩意在使用工具自带的编译器时不会生成实现类,须要经过 maven 的方式来进行编译,而后才会生成实现类。

因此咱们须要增长一个编译插件到 pom 文件中:

<!-- 引入 processor -->
<dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct-processor</artifactId>
      <version>1.3.0.Final</version>
      <scope>provided</scope>
</dependency>
<!--为 Maven compile plugin 设置 annotation processor -->
<plugins>
	<plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.5.1</version>
          <configuration>
            <source>1.8</source>
            <target>1.8</target>
            <annotationProcessorPaths>
              <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.2.0.Final</version>
              </path>
            </annotationProcessorPaths>
          </configuration>
	</plugin>
</plugins>
复制代码

而后咱们运行程序就能够获得本身想要的结果了

file

安装 MapStruct 插件

使用 MapStruct,还有一个缺点就是,当属性更名的时候,由于在 Mapper 上注解中配置的名字是在字符串里面,所以不会自动同步的。因此 MapStruct 提供了一个插件来解决这个问题,同时还提供代码自动提示、点击跳转到实现等功能。

关于插件的更多信息,参见 MapStruct support for IntelliJ IDEA

安装插件的过程

在 IDEA 中依次打开 File - > Settings - > Plugins

而后在 Markeyplace 搜索框中输入 mapstruct,点击 install,而后重启 IDE 便可。

file

一些可能会出现的问题

  • 找不到注释处理程序:在 pom.xml 中增长 mapstruct-processor 的依赖

  • 没有找到实现类:在 pom.xml 中加入对 mapstruct-processor 的依赖

  • 在 IDEA 里面 enable Annotation Processor

  • 使用 Lombok 的状况下,编译时报 Data 类的 setter/getter 找不到:把 lombok 加入到annotationProcessorPath,以下图

file

总结

MapSturct 是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。

做为一个注解处理器, 经过MapStruct 生成的代码具备怎么样的优点呢?抓一下重点:

  1. 注解处理器
  2. 能够生成 JavaBean 之间的映射代码
  3. 类型安全, 高性能, 无依赖性

高性能

这是相对反射来讲的, 反射须要去读取字节码的内容, 花销会比较大。 而经过 MapStruct 来生成的代码, 其相似于人手写,代码执行速度上能够获得保证。(前面例子中生成的代码能够在编译后看到,在项目的 target/generated-sources/annotations 目录里能够看到具体代码)。

易于 debug

在咱们生成的代码中, 咱们能够轻易的进行 debug。可是若是是使用反射实现代码的时候, 一旦出现了问题, 不少时候是比较难找到缘由。

使用相对简单

若是是彻底映射的, 使用起来确定没有反射简单。 用相似 BeanUtils 这些工具一条语句就搞定了。 可是,若是须要进行特殊的匹配(特殊类型转换, 多对一转换等), MapStruct 的优点就比较明显了,基本上咱们只须要在使用的时候声明一个接口, 接口下写对应的方法, 就可使用了(固然, 若是有特殊状况, 是须要额处理一下的)。

代码独立

生成的代码是对立的, 没有运行时的依赖


本文首发于微信公众号 【程序猿杂货铺】,关注公众号,获取更多精彩文章!

欢迎关注个人公众号
相关文章
相关标签/搜索