丢弃掉那些BeanUtils工具类吧,MapStruct真香!!!
在前几天的文章《为什么阿里巴巴禁止使用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的引用。这部分在后文会再次介绍。
之后,我们需要定义一个做映射的接口,主要代码如下:
@Mapper
interface 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;
}
@Mapper
interface 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:
@Mapper
interface 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 {
@Override
public 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,他的用法比较简单,并且功能非常完善,可以应付各种情况的字段映射。
并且因为他是编译期就会生成真正的映射代码,使得运行期的性能得到了大大的提升。
强烈推荐,真的很香!!!
丢弃掉那些BeanUtils工具类吧,MapStruct真香!!!的更多相关文章
- 第13天 JSTL标签、MVC设计模式、BeanUtils工具类
第13天 JSTL标签.MVC设计模式.BeanUtils工具类 目录 1. JSTL的核心标签库使用必须会使用 1 1.1. c:if标签 1 1.2. c:choos ...
- 利用BeanUtils工具类封装表单数据
一.BeanUtils工具类的使用 1.首先导入BeanUtils工具类的jar包 commons-beanutils-1.8.0.jar commons-logging-1.1.1.jar 2.se ...
- BeanUtils 工具类
一.BeanUtils 概述 BeanUtils 是阿帕奇提供的一套专门用于将一些数据封装到java对象中的工具类; 名词:javaBean:特定格式的java类称为java ...
- JavaWeb 之 BeanUtils 工具类
在上一个用户登录案例中,当从浏览器接收参数后,还需要创建 JavaBean 类,对其的属性每一项赋值,如果属性少,可以手动完成,但是当属性非常多,这时就发现非常不方便,在这里提供一个可以封装 Java ...
- JDBC--使用beanutils工具类操作JavaBean
1.在JavaEE中,Java类的属性通过getter,setter来定义: 2.可使用BeanUtils工具包来操作Java类的属性: --Beanutils是由Apache公司开发,能够方便对Be ...
- BeanUtils工具类copyProperties方法缺点及解决
使用类为spring-beans:4.3.13release包中的 org.springframework.beans.BeanUtils BeanUtils.copyProperties(Objec ...
- 使用BeanUtils工具类操作Java bean
1.类的属性: 1).在Java EE中,类的属性通过setter和getter定义:类中的setter(getter)方法去除set(get)后剩余的部分就是类的属性 2).而之前叫的类的属性,即成 ...
- 内省(二)之BeanUtils工具类
上一篇内省(Introspector)讲到的是采用JavaAPI中的类来操作bean及其属性,而Apache也开源了第三方框架来简化和丰富了对bean属性的操作,这个框架就是BeanUtils. 使用 ...
- BeanUtils工具类
用对象传参,用JavaBean传参. BeanUtils可以优化传参过程. 学习框架之后,BeanUtils的功能都由框架来完成. 一.为什么用BeanUtils? 每次我们的函数都要传递很多参数很麻 ...
随机推荐
- PHP实现多继承
题问php是否支持多继承? 答案:不可以,只支持单继承. 如何实现多继承呢? 答案:可以使用 interface 或 trait 实现 . interface这里我们就不做过多的说明了,它的原理就是一 ...
- Python Ethical Hacking - MAC Address & How to Change(3)
SIMPLE ALGORITHM Goal -> Check if MAC address was changed. Steps: 1. Execute and read ifconfig. ...
- C++算法 链式前向星存图
这个东西恶心了我一阵子,那个什么是什么的上一个一直是背下来的,上次比赛忘了,回来有个题也要用,只能再学一遍,之前也是,不会为什么不学呢.我觉得是因为他们讲的不太容易理解,所以我自己给那些不会的人们讲一 ...
- ajax原生js封装
不带注释的 function ajax(json) { json.type = json.type ? json.type : 'get'; json.async = json.async == fa ...
- Puppeteer爬虫实战(三)
本篇文章针对大家熟知的技术站点作为目标进行技术实践. 确定需求 访问目标网站并按照筛选条件(关键词.日期.作者)进行检索并获取返回数据中的目标数据.进行技术拆分如下: 打开目标网站 找到输入框元素 ...
- IO、NIO实现简单聊天室,附带问题解析
本篇文章主要使用IO和NIO的形式来实现一个简单的聊天室,并且说明IO方法存在的问题,而NIO又是如何解决的. 大概的框架为,先提供思路和大概框架图--代码--问题及解决方式,这样会容易看一点 ...
- shell脚本带参数启动项目
用maven工程打包时,会将数据库连接一并打进去,如果需要经常修改数据库连接,则需要打开jar包然后修改配置,这样很麻烦耗时并且容易出错. 因此需要将数据库配置放入项目外,这样修改数据库时去固定的配置 ...
- 循序渐进nginx(三):日志管理、http限流、https配置,http_rewrite模块,第三方模块安装,结语
目录 日志管理 access_log error_log 日志文件切割 自定义错误页 http访问限流 限制请求数 语法 使用 限制连接数 语法 测试 补充: https配置 使用 生成证书 配置ng ...
- C#计算数组的算术平均数、几何平均数、调和平均数、平方平均数和中位数
1.函数实现 0)打印数组 /// <summary> /// 打印数组 /// </summary> /// <param name="arr"&g ...
- set自动排序去重 stringstream流分割字符
链接:https://vjudge.net/problem/UVA-10815#author=0 题意:给几段句子,按字典序筛选出单词. 题解:用C的话太麻烦,不如用自动去重并排序的set容器.有个地 ...