简介

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

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

业务场景

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

实现方式之使用传统方式

如下:

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.java

@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.java

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

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

运行结果

上边的代码或许暂时看起来还是比较简洁的,但是我们需要注意的一点就是平时业务开发中的对象属性远不是上述代码中简简单单的几个字段,有可能会有数十个字段,同理也会数十个对象需要转换,我们如果还是通过 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);
}

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

核心是这一句 :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>

然后我们运行程序就可以得到自己想要的结果了

安装 MapStruct 插件

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

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

安装插件的过程

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

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

一些可能会出现的问题

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

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

  • 在 IDEA 里面 enable Annotation Processor

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

总结

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

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

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

高性能

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

易于 debug

在我们生成的代码中, 我们可以轻易的进行 debug。但是如果是使用反射实现代码的时候, 一旦出现了问题, 很多时候是比较难找到原因。

使用相对简单

如果是完全映射的, 使用起来肯定没有反射简单。 用类似 BeanUtils 这些工具一条语句就搞定了。 但是,如果需要进行特殊的匹配(特殊类型转换, 多对一转换等), MapStruct 的优势就比较明显了,基本上我们只需要在使用的时候声明一个接口, 接口下写对应的方法, 就可以使用了(当然, 如果有特殊情况, 是需要额处理一下的)。

代码独立

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


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

业务代码的救星——Java 对象转换框架 MapStruct 妙用的更多相关文章

  1. 本地C代码中创建Java对象

    作者:唐老师,华清远见嵌入式学院讲师. 创建Java域的对象就是创建Java类的实例,再调用Java类的构造方法. 以Bitmap的构建为例,Bitmap中并没有Java对象创建的代码及外部能访问的构 ...

  2. Java编码辅助工具:Mapstruct—— Java对象转换框架

    项目开发中,业务分层会涉及不同类型的Bean之间需要相互转换,如PO与DTO之间,PO与VO之间等.手动编码setter/getter各个对应属性,会显得臃肿繁琐.通过Mapstruct框架可简单方便 ...

  3. IntelliJ IDEA平台下JNI编程(五)—本地C代码创建Java对象及引用

    本文学习如何在C代码中创建Java对象和对象数组,前面我们学习了C代码中访问Java对象的属性和方法,其实在创建对象时本质上也就是调用构造函数,因此本文知识学习起来也很轻松.有了前面学习数组创建的方法 ...

  4. JSON-lib框架,JAVA对象与JSON、XML之间的相互转换

    Json-lib可以将Java对象转成json格式的字符串,也可以将Java对象转换成xml格式的文档,同样可以将json字符串转换成Java对象或是将xml字符串转换成Java对象. 一. 准备工作 ...

  5. 使用XStream注解实现Java对象与XML互相转换的代码示例

    本文记录一下使用xstream这个api的注解特性对Java对象与XML字符串相互转换的一些代码示例.    我们很多人都处理过XML文件,也有很多非常成熟的第三方开源软件.如:jdom.dom4j等 ...

  6. java 对象的初始化流程(静态成员、静态代码块、普通代码块、构造方法)

    一.java对象初始化过程 第一步,加载该类,一个java对象在初始化前会进行类加载,在JVM中生成Class对象.加载一个类会进行如下操作,下面给出递归描述.(关于Class对象详见反射 点击这里) ...

  7. JVM 性能调优实战之:使用阿里开源工具 TProfiler 在海量业务代码中精确定位性能代码

    本文是<JVM 性能调优实战之:一次系统性能瓶颈的寻找过程> 的后续篇,该篇介绍了如何使用 JDK 自身提供的工具进行 JVM 调优将 TPS 由 2.5 提升到 20 (提升了 7 倍) ...

  8. 编写高质量代码:改善Java程序的151个建议(第二章:基本类型)

    编写高质量代码:改善Java程序的151个建议(第二章:基本类型) 目录 建议21:用偶判断,不用奇判断 建议22:用整数类型处理货币 建议23:不要让类型默默转换 建议24:边界还是边界 建议25: ...

  9. 编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则)

    编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则) 目录 建议1: 不要在常量和变量中出现易混淆的字母 建议2: 莫让常量蜕变成变量 建议3: 三元操作符的类型务 ...

随机推荐

  1. 动态规划_Sumsets_POJ-2229

    Farmer John commanded his cows to search . Here are the possible sets of numbers that sum to : ) +++ ...

  2. jsp数据交互(一).3

    引入的文件如果是jsp则应定义为***.jspf文件,如果其他文件可定义为***.inc文件,即include file. jsp:include是既可以静态包含又可以动态包含,用jsp:includ ...

  3. Jibx 只绑定需要的字段

    栗子:     binding.xml   <?xml version="1.0" encoding="UTF-8"?> <binding&g ...

  4. Python解释器安装教程和环境变量配置

    Python解释器安装教程和环境变量配置 Python解释器安装 登录Python的官方网站   https://www.python.org/  进行相应版本的下载. 第一步:根据电脑系统选择软件适 ...

  5. 【WPF】 InkCanvas 书写毛笔效果

    首先贴出本文参考学习的文章吧. https://www.cnblogs.com/LCHL/p/9055642.html#4206298 感谢这位懒羊羊的代码和讲解(下简称羊博主),我在此基础上稍微加了 ...

  6. 使用vsftp与shell实现对进程与服务状态的监控

    先说一下需求吧,公司开发了一款新的产品,新产品嘛,有着不得不出问题的理由,四个云机房,总共三百余台机器,需要实时的监控进程状态,虽然有zabbix来实现,但领导需求是脚本和zabbix一起做,zabb ...

  7. 记录eclipse中文出现空格宽度不一致的bug

    起因 不久前更新了 eclipse(2019-03) 版本:突然发现出现了,使用注释使用中出现的空格的间隔大小不一致的问题,具体可以看下图: 遇到这种问题简直逼不能忍,在网上搜一下解决方式: 谷歌 搜 ...

  8. Log4j 2 配置

    版本区别 Log4j 2 与 log4j 1.x 最大的区别在于,新版本的 log4j 2 只支持 json 与 xml,不再支持以前的 properties 资源文件 下载 log4j 的jar 包 ...

  9. LR(1)语法分析器生成器(生成Action表和Goto表)java实现(一)

    序言 : 在看过<自己实现编译器链接器>源码之后,最近在看<编译器设计>,但感觉伪代码还是有点太浮空.没有掌握的感觉,也因为内网几乎没有LR(1)语法分析器生成器的内容,于是我 ...

  10. String——字符串

    首先看一下string的一部分源码吧 public final class String private final char value[]; 我们暂且只看这两行, 第一行String被final修 ...