点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试文章

在前几天的文章《为什么阿里巴巴禁止使用Apache Beanutils进行属性的copy?》中,我曾经对几款属性拷贝的工具类进行了对比。

然后在评论区有些读者反馈说MapStruct才是真的香,于是我就抽时间了解了一下MapStruct。结果我发现,这真的是一个神仙框架,炒鸡香。

这一篇文章就来简单介绍下MapStruct的用法,并且再和其他几个工具类进行一下对比。

为什么需要MapStruct ?

首先,我们先说一下MapStruct这类框架适用于什么样的场景,为什么市面上会有这么多的类似的框架。

在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。很多人都对三层架构、四层架构等并不陌生。

甚至有人说:"计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,如果不行,那就加两层。"

但是,随着软件架构分层越来越多,那么各个层次之间的数据模型就要面临着相互转换的问题,典型的就是我们可以在代码中见到各种O,如DO、DTO、VO等。

一般情况下,同样一个数据模型,我们在不同的层次要使用不同的数据模型。如在数据存储层,我们使用DO来抽象一个业务实体;在业务逻辑层,我们使用DTO来表示数据传输对象;到了展示层,我们又把对象封装成VO来与前端进行交互。

那么,数据的从前端透传到数据持久化层(从持久层透传到前端),就需要进行对象之间的互相转化,即在不同的对象模型之间进行映射。

通常我们可以使用get/set等方式逐一进行字段映射操作,如:

personDTO.setName(personDO.getName());personDTO.setAge(personDO.getAge());personDTO.setSex(personDO.getSex());personDTO.setBirthday(personDO.getBirthday());

但是,编写这样的映射代码是一项冗长且容易出错的任务。MapStruct等类似的框架的目标是通过自动化的方式尽可能多地简化这项工作。

MapStruct的使用

MapStruct(https://mapstruct.org/ )是一种代码生成器,它极大地简化了基于"约定优于配置"方法的Java bean类型之间映射的实现。生成的映射代码使用纯方法调用,因此快速、类型安全且易于理解。

约定优于配置,也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。

假设我们有两个类需要进行互相转换,分别是PersonDO和PersonDTO,类定义如下:

public class PersonDO {private Integer id;private String name;private int age;private Date birthday;private String gender;}public class PersonDTO {private String userName;private Integer age;private Date birthday;private Gender gender;}

我们演示下如何使用MapStruct进行bean映射。

想要使用MapStruct,首先需要依赖他的相关的jar包,使用maven依赖方式如下:

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

因为MapStruct需要在编译器生成转换代码,所以需要在maven-compiler-plugin插件中配置上对mapstruct-processor的引用。这部分在后文会再次介绍。

之后,我们需要定义一个做映射的接口,主要代码如下:

@Mapperinterface PersonConverter {PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);@Mappings(@Mapping(source = "name", target = "userName"))PersonDTO do2dto(PersonDO person);}

使用注解@Mapper定义一个Converter接口,在其中定义一个do2dto方法,方法的入参类型是PersonDO,出参类型是PersonDTO,这个方法就用于将PersonDO转成PersonDTO。

测试代码如下:

public static void main(String[] args) {PersonDO personDO = new PersonDO();personDO.setName("Hollis");personDO.setAge(26);personDO.setBirthday(new Date());personDO.setId(1);personDO.setGender(Gender.MALE.name());PersonDTO personDTO = PersonConverter.INSTANCE.do2dto(personDO);System.out.println(personDTO);}

输出结果:

PersonDTO{userName='Hollis', age=26, birthday=Sat Aug 08 19:00:44 CST 2020, gender=MALE}

可以看到,我们使用MapStruct完美的将PersonDO转成了PersonDTO。

上面的代码可以看出,MapStruct的用法比较简单,主要依赖@Mapper注解。

但是我们知道,大多数情况下,我们需要互相转换的两个类之间的属性名称、类型等并不完全一致,还有些情况我们并不想直接做映射,那么该如何处理呢?

其实MapStruct在这方面也是做的很好的。

MapStruct处理字段映射

首先,可以明确的告诉大家,如果要转换的两个类中源对象属性与目标对象属性的类型和名字一致的时候,会自动映射对应属性。

那么,如果遇到特殊情况如何处理呢?

名字不一致如何映射

如上面的例子中,在PersonDO中用name表示用户名称,而在PersonDTO中使用userName表示用户名,那么如何进行参数映射呢。

这时候就要使用@Mapping注解了,只需要在方法签名上,使用该注解,并指明需要转换的源对象的名字和目标对象的名字就可以了,如将name的值映射给userName,可以使用如下方式:

@Mapping(source = "name", target = "userName")

可以自动映射的类型

除了名字不一致以外,还有一种特殊情况,那就是类型不一致,如上面的例子中,在PersonDO中用String类型表示用户性别,而在PersonDTO中使用一个Genter的枚举表示用户性别。

这时候类型不一致,就需要涉及到互相转换的问题

其实,MapStruct会对部分类型自动做映射,不需要我们做额外配置,如例子中我们将String类型自动转成了枚举类型。

一般情况下,对于以下情况可以做自动类型转换:

  • 基本类型及其他们对应的包装类型。

  • 基本类型的包装类型和String类型之间

  • String类型和枚举类型之间

自定义常量

如果我们在转换映射过程中,想要给一些属性定义一个固定的值,这个时候可以使用 constant

@Mapping(source = "name", constant = "hollis")

类型不一致的如何映射

还是上面的例子,如果我们需要在Person这个对象中增加家庭住址这个属性,那么我们一般在PersonoDTO中会单独定义一个HomeAddress类来表示家庭住址,而在Person类中,我们一般使用String类型表示家庭住址。

这就需要在HomeAddress和String之间使用JSON进行互相转化,这种情况下,MapStruct也是可以支持的。

public class PersonDO {private String name;private String address;}public class PersonDTO {private String userName;private HomeAddress address;}@Mapperinterface PersonConverter {PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);@Mapping(source = "userName", target = "name")@Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")PersonDO dto2do(PersonDTO dto2do);default String homeAddressToString(HomeAddress address){return JSON.toJSONString(address);}}

我们只需要在PersonConverter中在定义一个方法(因为PersonConverter是一个接口,所以在JDK 1.8以后的版本中可以定义一个default方法),这个方法的作用就是将HomeAddress转换成String类型。

default方法:Java 8 引入的新的语言特性,用关键字default来标注,被default所标注的方法,需要提供实现,而子类可以选择实现或者不实现该方法

然后在dto2do方法上,通过以下注解方式即可实现类型的转换:

@Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")

上面这种是自定义的类型转换,还有一些类型的转换是MapStruct本身就支持的,如String和Date之间的转换:

@Mapping(target = "birthday",dateFormat = "yyyy-MM-dd HH:mm:ss")

以上,简单介绍了一些常用的字段映射的方法,也是我自己在工作中经常遇到的几个场景,更多的情况大家可以查看官方的示例(https://github.com/mapstruct/mapstruct-examples)。

MapStruct的性能

前面说了这么多MapStruct的用法,可以看出MapStruct的使用还是比较简单的,并且字段映射上面的功能很强大,那么他的性能到底怎么样呢?

参考《为什么阿里巴巴禁止使用Apache Beanutils进行属性的copy?》中的示例,我们对MapStruct进行性能测试。

分别执行1000、10000、100000、1000000次映射的耗时分别为:0ms、1ms、3ms、6ms。

可以看到,MapStruct的耗时相比较于其他几款工具来说是非常短的

那么,为什么MapStruct的性能可以这么好呢?

其实,MapStruct和其他几类框架最大的区别就是:与其他映射框架相比,MapStruct在编译时生成bean映射,这确保了高性能,可以提前将问题反馈出来,也使得开发人员可以彻底的错误检查。

还记得前面我们在引入MapStruct的依赖的时候,特别在maven-compiler-plugin中增加了mapstruct-processor的支持吗?

并且我们在代码中使用了很多MapStruct提供的注解,这使得在编译期,MapStruct就可以直接生成bean映射的代码,相当于代替我们写了很多setter和getter。

如我们在代码中定义了以下一个Mapper:

@Mapperinterface PersonConverter {PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);@Mapping(source = "userName", target = "name")@Mapping(target = "address",expression = "java(homeAddressToString(dto2do.getAddress()))")@Mapping(target = "birthday",dateFormat = "yyyy-MM-dd HH:mm:ss")PersonDO dto2do(PersonDTO dto2do);default String homeAddressToString(HomeAddress address){return JSON.toJSONString(address);}}

经过代码编译后,会自动生成一个PersonConverterImpl:

@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2020-08-09T12:58:41+0800",comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)")class PersonConverterImpl implements PersonConverter {@Overridepublic PersonDO dto2do(PersonDTO dto2do) {if ( dto2do == null ) {return null;}PersonDO personDO = new PersonDO();personDO.setName( dto2do.getUserName() );if ( dto2do.getAge() != null ) {personDO.setAge( dto2do.getAge() );}if ( dto2do.getGender() != null ) {personDO.setGender( dto2do.getGender().name() );}personDO.setAddress( homeAddressToString(dto2do.getAddress()) );return personDO;}}

在运行期,对于bean进行映射的时候,就会直接调用PersonConverterImpl的dto2do方法,这样就没有什么特殊的事情要做了,只是在内存中进行set和get就可以了。

所以,因为在编译期做了很多事情,所以MapStruct在运行期的性能会很好,并且还有一个好处,那就是可以把问题的暴露提前到编译期。

使得如果代码中字段映射有问题,那么应用就会无法编译,强制开发者要解决这个问题才行。

总结

本文介绍了一款Java中的字段映射工具类,MapStruct,他的用法比较简单,并且功能非常完善,可以应付各种情况的字段映射。

并且因为他是编译期就会生成真正的映射代码,使得运行期的性能得到了大大的提升。

强烈推荐,真的很香!!!

热门内容:一款直击痛点的优秀http框架,让我超高效率完成了和第三方接口的对接总在说SpringBoot内置了tomcat启动,那它的原理你说的清楚吗?
Hutool,一个贼好用的 Java 工具类库,用过都说好~记一次由Redis分布式锁造成的重大事故,避免以后踩坑!最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ

丢弃掉那些BeanUtils工具类吧,MapStruct真香!!!相关推荐

  1. BeanUtils工具类,简化数据封装

     login.html中form表单的action路径的写法         * 虚拟目录+Servlet的资源路径 BeanUtils工具类,简化数据封装         * 用于封装JavaBea ...

  2. BeanUtils工具类

    BeanUtils工具类,简化数据封装 用于封装JavaBean的 JavaBean:标准的Java类 要求: 1. 类必须被public修饰 2. 必须提供空参的构造器 3. 成员变量必须使用pri ...

  3. beanutils工具类_Apache Commons 工具类介绍及简单使用

    来源:http://h5ip.cn/9xu3 Apache Commons 工具类大家都有用过,但是可能缺乏系统学习,只用到了一小部分功能,无法发挥极限的价值,肥朝用大白话说就是,一颗好白菜都让猪给拱 ...

  4. beanutils工具类_16 个超级实用的 Java 工具类!

    在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法按使用流行度排名,参考数据来源于Github上随机选取的5万个开源项目源码. 一. ...

  5. spring的beanutils工具类_基于spring-beans实现工具类BeanUtils基于Class实例化注入对象及查找方法、复制属性等操作...

    一.前言 基于spring-beans(4.1.4)的工具类org.springframework.beans.BeanUtils对注入spring对象按照Class实例化instantiateCla ...

  6. 换掉 Java 8 Java 1718 新特性真香

    Java 17 Java 17 在 2021 年 9 月 14 日正式发布,Java 17 是一个长期支持(LTS)版本,这次更新共带来 14 个新功能. JEP 306:恢复始终严格的浮点语义 既然 ...

  7. 二、java项目常用工具类之beancopy,bean和map转换工具类

    项目环境: jdk1.8+spring4.3.12 一.问题描述及试用场景: 在项目规范中,要求类名以DO为尾的类作为数据库层实体bean,类名以MO为尾的类作为系统传输层实体bean,类名以VO为尾 ...

  8. StatusBarUtil 状态栏工具类

    0: 项目中遇到需求,需要改变状态栏,发现一个工具类很好用,写篇文章记录下. 1: StatusBarUtil一个关于状态栏操作起来超简单工具类 开个传送门 https://github.com/la ...

  9. 常规工具类:GeneralUtils.java

    为了提高日常项目中,部分代码的复用性,创建了该常规工具类 工具类所需的依赖 <!-- 处理JSON数据 --><dependency><groupId>com.al ...

最新文章

  1. 如何查看Apache的连接数和当前连接数
  2. AI一分钟 | ​被大家吐槽的网红机器人索菲亚开微博了;阿里人工智能团队iDST获得道路场景分割三项第一
  3. SVN的搭建及使用(三)用TortoiseSVN修改文件,添加文件,删除文件,以及如何解决冲突,重新设置用户名和密码等...
  4. .NET小笔记之程序集
  5. 托管数据中心之间的PUE比较(下)
  6. 多线程常见问题(面试)
  7. Codeforces 448 D. Multiplication Table
  8. java eclipse 监视选择指定变量
  9. teablue数据分析_Bluetea蓝茶的品牌该如何分析,你知道吗
  10. Spring事务原理分析(三)--事务代理调用过程
  11. B2B 网关软件 以新颖的模式 让企业步入新常态
  12. RestClient 接口测试实践
  13. 【干货】9个网络故障排除经典案例,网工都得会
  14. 高德地图Amap常用功能总结
  15. 如何对待新事物_以积极态度看待不断出现的新事物
  16. sql 闩锁 原因_关于SQL Server中的闩锁
  17. 心理学 | (1)焦虑症和恐惧症--一种认知的观点
  18. 【运维工程师主要做哪些工作】运维工程师
  19. C#_____找错误
  20. Python实用脚本/算法集合, 附源代码下载

热门文章

  1. openstack高可用方案
  2. MyBatis中jdbcType=INTEGER、VARCHAR作用
  3. django 增加验证邮箱功能
  4. Mysql 多表使用 Case when then 遇到的坑
  5. 01--安装Activiti流程设计器eclipse插件
  6. Alpha阶段项目总结
  7. MAC OS X El CAPITAN 搭建SPRING MVC (1)- 目录、包名、创建web.xml
  8. Java Socket发送与接收HTTP消息简单实现
  9. 25 年汽车技术老兵亲述,自动驾驶新驶向
  10. 关于GCN,我有三种写法