在业务项目的开发中,我们经常需要将 Java 对象进行转换,比如从将外部微服务得到的对象转换为本域的业务对象 domainobject,将 domainobject 转为数据持久层的 dataobject,将 domainobject 转换为 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 对象*/@Beforepublic void before() {Role role  = new Role(2L, "administrator", "超级管理员");user  = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);}/*** 模拟把 user 对象转换成 UserRoleDto 对象*/@Testpublic 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对象*/@Testpublic void test2() {UserRoleDto userRoleDto = UserRoleMapper.INSTANCES.toUserRoleDto(user);System.out.println(userRoleDto);}

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

核心是这一句 :java.lang.ClassNotFoundException:Cannotfind implementationfortop.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 的优势就比较明显了,基本上我们只需要在使用的时候声明一个接口, 接口下写对应的方法, 就可以使用了(当然, 如果有特殊情况, 是需要额处理一下的)。

代码独立

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


原作者:zhoudl
原文链接:业务代码的救星——Java 对象转换框架 MapStruct 妙用
原出处:公众号

定义一个dto对象_业务代码的救星——Java 对象转换框架 MapStruct 妙用相关推荐

  1. java map 结构体_业务代码的救星——Java 对象转换框架 MapStruct 妙用

    简介 在业务项目的开发中,我们经常需要将 Java 对象进行转换,比如从将外部微服务得到的对象转换为本域的业务对象 domain object,将 domain object 转为数据持久层的 dat ...

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

    介绍 在业务项目的开发中,我们经常需要将Java对象进行转换,比如从外部HSF服务得到的对象转换为本域的业务对象domain object,将domain object转为数据持久层的data obj ...

  3. java定义一个eat方法_小黄鸭系列java基础知识 | java中的方法

    前言 今天我们要探讨的问题,是java基础语法的最后一个问题,也就是java中的方法,今天主要从以下几个方面来介绍: 方法是什么(定义) 方法的分类 方法的调用 应该说,学完今天的知识,你至少应该看懂 ...

  4. java对象转xml 高性能_xml与java对象的快速互转

    做流程图的项目时,新的流程定义为xml的,需要对xml与java对象进行互转 查了一下activiti的转换xml方式,发现转换太麻烦了,需要一步步的解析xml 后面发现直接用jaxb就可以很快实现互 ...

  5. 定义一个dto对象_正确理解DTO、值对象和POCO

    (此文章同时发表在本人微信公众号"dotNET每日精华文章") 今天推荐的文章比较技术化也比较简单,但是对于一些初学者而言,可能也是容易搞混的概念:就是如何理解DTO.值对象和PO ...

  6. 定义一个dto对象_java里面Dto对象跟VO的区别

    经常会接触到VO,DO,DTO的概念,本文从领域建模中的实体划分和项目中的实际应用情况两个角度,对这几个概念进行简析. 得出的主要结论是:在项目应用中,VO对应于页面上需要显示的数据(表单),DO对应 ...

  7. c++定义一个动态全局变量_静态链接与动态链接的宏观概述及微观详解

    静态链接与动态链接的宏观概述及微观详解 第一部分 宏观概述 1. 静态链接 静态链接就是在程序运行前,链接器通过对象文件中包含的重定位表,完成所有重定位操作,并最终形成一个在运行时不需要再次进行依赖库 ...

  8. 定义一个空切片_全面解读Python高级特性切片

    大家好,欢迎来到Crossin的编程教室! 众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串.列表.元组-)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢? 切片(slic ...

  9. sqlserver如何定义一个静态变量_[Bazel]自定义规则实现将多个静态库合并为一个动态库或静态库...

    1 前言 2 自定义规则实现 2.1 规则功能 2.2 实现规则的理论基础 2.3 规则代码实现 3 总结 4 参考资料 1 前言 为了实现如标题所述的将多个静态库合并为一个动态库,内置的 Bazel ...

最新文章

  1. 推荐五星级C语言学习网站
  2. Linux vi 中移动光标 命令
  3. javase(Properties集合及学生对象信息录入文本中案例)
  4. 使用谷歌浏览器模拟微信(android或ios)浏览器
  5. 一个NVIDIA驱动安装报错——ERROR: The nvidia kernel module was not created.
  6. Vim的基本操作总结
  7. JAVA导入导出Excel
  8. 易天ETU-link 100G QSFP28光模块系列资料
  9. 应用程序按照以下顺序执行由 global.asax 文件中定义的模块或用户代码处理的事件...
  10. 编译原理文法等价变换
  11. 怎么用服务器跑matlab程序,服务器上跑matlab程序
  12. natapp 使用教程
  13. 秋无痕 Windows 7 SP1 (64位旗舰版) 集成安装增强版 V2018年春节版(整合USB3+NVMe+UEFI)
  14. LOGO特训营 第四节 字体设计的重要性
  15. Spark机器学习实验
  16. 对不起,今年我真的不敢去拜年了。。。
  17. CGI的基本定义和优劣势是什么
  18. (转)在路上—Tinyfool的程序员生涯(职业生涯篇一)
  19. 一花独放不是春 梆梆安全呼吁构建物联网安全共同体
  20. 计算机控制系统编程语言,PLC的五种主要编程语言是什么?

热门文章

  1. jquery事件绑定解绑机制源码分析
  2. linux链接达梦数据库,linux下面 达梦数据库的JDBC链接
  3. android 对话框 图片,android – AlertDialog按钮的图像
  4. vue 给iframe设置src_vue组件中使用iframe元素
  5. WINCE BSP中source文件中的宏定义
  6. linux-2.6.32.2内核在mini2440上的移植,Linux2.6.32.2移植到Mini2440
  7. linux 计划任务 实例,计划任务 cron 的配置和实例
  8. 【转】VScode快捷键(超无敌详细版)
  9. 【转】__declspec用法详解
  10. 【转】国密算法sm4 CBC模式加解密