网易首页 > 网易号 > 正文 申请入驻

丢弃掉那些 BeanUtils 工具类吧,MapStruct 真香!!!

0
分享至

  喜欢就关注我们吧!

  之前写过一篇文章《为什么阿里巴巴禁止使用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依赖方式如下:

  ...

  1.3.1.Finalorg.mapstruct.version>

  properties>

  ...

  org.mapstructgroupId>

  mapstructartifactId>

  ${org.mapstruct.version}version>

  dependency>

  dependencies>

  ...

  org.apache.maven.pluginsgroupId>

  maven-compiler-pluginartifactId>

  3.8.1version>

  1.8source>

  1.8target>

  org.mapstructgroupId>

  mapstruct-processorartifactId>

  ${org.mapstruct.version}version>

  path>

  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的使用还是比较简单的,并且字段映射上面的功能很强大,那么它的性能到底怎么样呢?

  参考《》中的示例,我们对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,它的用法比较简单,并且功能非常完善,可以应付各种情况的字段映射。

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

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

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
彻底撕破脸!梅西姆巴佩都爆发了,葡萄牙球员却忙着逼走C罗!

彻底撕破脸!梅西姆巴佩都爆发了,葡萄牙球员却忙着逼走C罗!

阿器谈史
2026-06-19 13:53:48
2026年夏天高温定局!最热时段已敲定,救命级防暑安排来了!

2026年夏天高温定局!最热时段已敲定,救命级防暑安排来了!

老特有话说
2026-06-18 14:50:51
浓眉:想搞追梦心态很容易 只需说他穷并且只能在发展联盟打球

浓眉:想搞追梦心态很容易 只需说他穷并且只能在发展联盟打球

北青网-北京青年报
2026-06-19 11:35:08
端午不能穿几种颜色衣服,穿了人不安,端午节有忌讳,看看是哪种

端午不能穿几种颜色衣服,穿了人不安,端午节有忌讳,看看是哪种

周哥一影视
2026-06-19 11:28:45
属牛人注意:6 月中下旬,家中会有异事发生,皆是天意暗示

属牛人注意:6 月中下旬,家中会有异事发生,皆是天意暗示

宝哥精彩赛事
2026-06-17 13:21:20
G7会议结束未发联合公报,德法不愿得罪中国时代终结

G7会议结束未发联合公报,德法不愿得罪中国时代终结

今夜繁星坠落
2026-06-19 14:12:22
现货黄金跌破4150美元,日内下跌1.45%

现货黄金跌破4150美元,日内下跌1.45%

每日经济新闻
2026-06-19 12:07:19
卡纳瓦罗:1-3告负的结果有些苦涩,我们得在执行力层面继续提高

卡纳瓦罗:1-3告负的结果有些苦涩,我们得在执行力层面继续提高

懂球帝
2026-06-18 15:06:15
正义来的太晚了!如今62岁已经退休的朱军,终于看到女方受到惩罚

正义来的太晚了!如今62岁已经退休的朱军,终于看到女方受到惩罚

青橘罐头
2026-06-03 09:45:45
高市狂不了了,日本天皇发出警告,接班人已浮现,对华态度不简单

高市狂不了了,日本天皇发出警告,接班人已浮现,对华态度不简单

风流女汉
2026-06-18 22:26:30
官方:皇家马德里免签科纳特至2030

官方:皇家马德里免签科纳特至2030

体坛周报
2026-06-18 17:19:38
WTA官宣:郑钦文获巴特洪堡赛外卡 此前表态若需打资格赛将放弃

WTA官宣:郑钦文获巴特洪堡赛外卡 此前表态若需打资格赛将放弃

醉卧浮生
2026-06-19 08:14:47
留洋告一段落!樊振东回国公开亮相新身份,他将投入到技术创新

留洋告一段落!樊振东回国公开亮相新身份,他将投入到技术创新

林子说事
2026-06-19 10:23:47
游泳冠军赛预赛综述:孙杨100自加赛晋级,张雨霏第一100蝶第一

游泳冠军赛预赛综述:孙杨100自加赛晋级,张雨霏第一100蝶第一

乒烧泳球
2026-06-19 13:18:32
端午插艾别选错 分清“艾”与“蒿”

端午插艾别选错 分清“艾”与“蒿”

环球网资讯
2026-06-19 08:03:12
俄军图22轰炸机坠毁!莫斯科战略武器损失不可逆

俄军图22轰炸机坠毁!莫斯科战略武器损失不可逆

项鹏飞
2026-06-16 21:14:34
黄金、白银,双双大跌

黄金、白银,双双大跌

环球网资讯
2026-06-19 14:27:20
“甘油三酯大户”被揪出,提醒:50岁后,这7种食物建议少吃

“甘油三酯大户”被揪出,提醒:50岁后,这7种食物建议少吃

岐黄传人孙大夫
2026-06-18 20:35:05
蒙特拉:没想到批评声会到这种程度,我们应得到更多尊重

蒙特拉:没想到批评声会到这种程度,我们应得到更多尊重

懂球帝
2026-06-19 14:16:47
上千吨香蕉烂在手里,菲律宾香蕉协会:中国断了30万蕉农的生计!

上千吨香蕉烂在手里,菲律宾香蕉协会:中国断了30万蕉农的生计!

楠楠自语
2026-05-05 21:48:09
2026-06-19 14:55:00
开源中国 incentive-icons
开源中国
每天为开发者推送最新技术资讯
7817文章数 34546关注度
往期回顾 全部

科技要闻

Anthropic被禁,智谱却涨疯了

头条要闻

美高官"反思":中方想要更多美国市场 美方却袖手旁观

头条要闻

美高官"反思":中方想要更多美国市场 美方却袖手旁观

体育要闻

加拿大球员小腿变形重伤 亚洲冠军输球输人

娱乐要闻

吴倩自曝小时被爸爸打掉牙齿硬吞进肚

财经要闻

Token低价陷阱

汽车要闻

惊出冷汗!重庆实测奥迪A5L,华为智驾这波操作绝了…

态度原创

旅游
手机
数码
家居
公开课

旅游要闻

端午假期贵阳贵安推出百余场活动

手机要闻

Pixel 10手机用户反馈AI“抢镜”问题,Gmail无法正常回复邮件

数码要闻

定义未来赛道:京东方如何用630亿重塑中尺寸OLED底牌?

家居要闻

绿意盎然 自然之境

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版