干掉 BeanUtils!试试这款 Bean 自动映射工具

2024-04-16 19:48

本文主要是介绍干掉 BeanUtils!试试这款 Bean 自动映射工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

开发背景

你有没有遇到过这样的开发场景?

服务通过接口对外提供数据,或者服务之间进行数据交互,首先查询数据库并映射成数据对象(XxxDO)。

正常情况下,接口是不允许直接以数据库数据对象 XxxDO 形式对外提供数据的,而是要再封装成数据传输对象(XxxDTO)提供出去。

为什么不能直接提供 DO?

1)根据单一设计原则,DO 只能对应数据实体对象,不能承担其他职责;

2)DO 可能包含表所有字段数据,不符合接口的参数定义,数据如果过大会影响传输速度,也不符合数据安全原则;

3)根据《阿里 Java 开发手册》分层领域模型规约,不能一个对象走天下,需要定义成 POJO/DO/BO/DTO/VO/Query 等数据对象,完整的定义可以参考阿里开发手册,关注公众号:Java技术栈,在后台回复:手册,可以获取最新高清完整版。

传统 DO -> DTO 做法

XxxDTO 可能包含 XxxDO 大部分数据,或者组合其他 DO 的部分数据,传统的做法有以下几种:

  • get/ set

  • 构造器

  • BeanUtils 工具类

  • Builder 模式

我相信大部分人的做法都是这样的,虽然很直接,但是普遍真的很 Low,耦合性又强,还经常丢参数,或者搞错参数值,在这个开发场景,我个人觉得这些都不是最佳的方式。

这种开发场景又实在是太常见了,那有没有一种 Java bean 自动映射工具?

没错——正是 MapStruct!!

MapStruct 简介

官网地址:

https://mapstruct.org/

开源地址:

https://github.com/mapstruct/mapstruct

图片

Java bean mappings, the easy way!

以简单的方式进行 Java bean 映射。

MapStruct 是一个代码生成器,它和 Spring Boot、Maven 一样也是基于约定优于配置的理念,极大地简化了 Java bean 之间数据映射的实现。

MapStruct 的优势:

1、MapStruct 使用简单的方法调用生成映射代码,因此***速度非常快***;

2、类型安全,避免出错,只能映射相互映射的对象和属性,因此不会错误将用户实体错误地映射到订单 DTO;

3、只需要 JDK 1.8+,不用其他任何依赖,自包含所有代码

4、易于调试

5、易于理解

支持的方式:

MapStruct 支持命令行编译,如:纯 javac 命令、Maven、Gradle、Ant 等等,也支持 Eclipse、IntelliJ IDEA 等 IDEs。

MapStruct 实战

本文栈长基于 IntelliJ IDEA、Spring Boot、Maven 进行演示。

基本准备

新增两个数据库 DO 类:

一个用户主类,一个用户扩展类。

/*** 微信公众号:Java技术栈* @author 栈长*/
@Data
public class UserDO {private String name;private int sex;private int age;private Date birthday;private String phone;private boolean married;private Date regDate;private Date loginDate;private String memo;private UserExtDO userExtDO;}
/*** 微信公众号:Java技术栈* @author 栈长*/
@Data
public class UserExtDO {private String regSource;private String favorite;private String school;private int kids;private String memo;}

新增一个数据传输 DTO 类:

用户展示类,包含用户主类、用户扩展类的部分数据。

/*** 微信公众号:Java技术栈* @author 栈长*/
@Data
public class UserShowDTO {private String name;private int sex;private boolean married;private String birthday;private String regDate;private String registerSource;private String favorite;private String memo;}

开始实战

重点来了,不要 get/set,不要 BeanUtils,怎么把两个用户对象的数据封装到 DTO 对象?

Spring Boot 基础这篇就不介绍了,系列基础教程和示例源码可以看这里:https://github.com/javastacks/spring-boot-best-practice

引入 MapStruct 依赖:

<dependencies><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${org.mapstruct.version}</version></dependency>
</dependencies>

Maven 插件相关配置:

MapStruct 和 Lombok 结合使用会有版本冲突问题,注意以下配置。

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version></path><!-- 使用 Lombok 需要添加 --><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${org.projectlombok.version}</version></path><!-- Lombok 1.18.16 及以上需要添加,不然报错 --><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>${lombok-mapstruct-binding.version}</version></path></annotationProcessorPaths></configuration></plugin></plugins>
</build>

添加 MapStruct 映射:

/*** 微信公众号:Java技术栈* @author 栈长*/
@Mapper
public interface UserStruct {UserStruct INSTANCE = Mappers.getMapper(UserStruct.class);@Mappings({@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")@Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))")@Mapping(source = "userExtDO.regSource", target = "registerSource")@Mapping(source = "userExtDO.favorite", target = "favorite")@Mapping(target = "memo", ignore = true)})UserShowDTO toUserShowDTO(UserDO userDO);List<UserShowDTO> toUserShowDTOs(List<UserDO> userDOs);}

重点说明:

1)添加一个 interface 接口,使用 MapStruct 的 @Mapper 注解修饰,这里取名 XxxStruct,是为了不和 MyBatis 的 Mapper 混淆;

2)使用 Mappers 添加一个 INSTANCE 实例,也可以使用 Spring 注入,后面会讲到;

3)添加两个映射方法,返回单个对象、对象列表;

4)使用 @Mappings + @Mapping 组合映射,如果两个字段名相同可以不用写,可以指定映射的日期格式、数字格式、表达式等,ignore 表示忽略该字段映射;

5)List 方法的映射会调用单个方法映射,不用单独映射,后面看源码就知道了;

另外,Java 8+ 以上版本不需要 @Mappings 注解,直接使用 @Mapping 注解就行了:

图片

Java 8 修改之后:

/*** 微信公众号:Java技术栈* @author 栈长*/
@Mapper
public interface UserStruct {UserStruct INSTANCE = Mappers.getMapper(UserStruct.class);@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")@Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))")@Mapping(source = "userExtDO.regSource", target = "registerSource")@Mapping(source = "userExtDO.favorite", target = "favorite")@Mapping(target = "memo", ignore = true)UserShowDTO toUserShowDTO(UserDO userDO);List<UserShowDTO> toUserShowDTOs(List<UserDO> userDOs);}

测试一下:

/*** 微信公众号:Java技术栈* @author 栈长*/
public class UserStructTest {@Testpublic void test1() {UserExtDO userExtDO = new UserExtDO();userExtDO.setRegSource("公众号:Java技术栈");userExtDO.setFavorite("写代码");userExtDO.setSchool("社会大学");UserDO userDO = new UserDO();userDO.setName("栈长");userDO.setSex(1);userDO.setAge(18);userDO.setBirthday(new Date());userDO.setPhone("18888888888");userDO.setMarried(true);userDO.setRegDate(new Date());userDO.setMemo("666");userDO.setUserExtDO(userExtDO);UserShowDTO userShowDTO = UserStruct.INSTANCE.toUserShowDTO(userDO);System.out.println("=====单个对象映射=====");System.out.println(userShowDTO);List<UserDO> userDOs = new ArrayList<>();UserDO userDO2 = new UserDO();BeanUtils.copyProperties(userDO, userDO2);userDO2.setName("栈长2");userDOs.add(userDO);userDOs.add(userDO2);List<UserShowDTO> userShowDTOs = UserStruct.INSTANCE.toUserShowDTOs(userDOs);System.out.println("=====对象列表映射=====");userShowDTOs.forEach(System.out::println);}
}

输出结果:

图片

来看结果,数据转换结果成功。

什么原理?

如上我们知道,通过一个注解修饰接口就可以搞定了,是什么原理呢?

来看编译后的目录:

图片

原理就是在编译期间生成了一个该接口的实现类。

打开看下其源码:

public class UserStructImpl implements UserStruct {public UserStructImpl() {}public UserShowDTO toUserShowDTO(UserDO userDO) {if (userDO == null) {return null;} else {UserShowDTO userShowDTO = new UserShowDTO();if (userDO.getBirthday() != null) {userShowDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userDO.getBirthday()));}userShowDTO.setRegisterSource(this.userDOUserExtDORegSource(userDO));userShowDTO.setFavorite(this.userDOUserExtDOFavorite(userDO));userShowDTO.setName(userDO.getName());userShowDTO.setSex(userDO.getSex());userShowDTO.setMarried(userDO.isMarried());userShowDTO.setRegDate(DateFormatUtils.format(userDO.getRegDate(), "yyyy-MM-dd HH:mm:ss"));return userShowDTO;}}public List<UserShowDTO> toUserShowDTOs(List<UserDO> userDOs) {if (userDOs == null) {return null;} else {List<UserShowDTO> list = new ArrayList(userDOs.size());Iterator var3 = userDOs.iterator();while(var3.hasNext()) {UserDO userDO = (UserDO)var3.next();list.add(this.toUserShowDTO(userDO));}return list;}}private String userDOUserExtDORegSource(UserDO userDO) {if (userDO == null) {return null;} else {UserExtDO userExtDO = userDO.getUserExtDO();if (userExtDO == null) {return null;} else {String regSource = userExtDO.getRegSource();return regSource == null ? null : regSource;}}}private String userDOUserExtDOFavorite(UserDO userDO) {if (userDO == null) {return null;} else {UserExtDO userExtDO = userDO.getUserExtDO();if (userExtDO == null) {return null;} else {String favorite = userExtDO.getFavorite();return favorite == null ? null : favorite;}}}
}

其实实现类就是调用了对象的 get/set 等其他常规操作,而 List 就是循环调用的该对象的单个映射方法,这下就清楚了吧!

Spring 注入法

上面的示例创建了一个 UserStruct 实例:

UserStruct INSTANCE = Mappers.getMapper(UserStruct.class);

如 @Mapper 注解源码所示:

图片

参数 componentModel 默认值是 default,也就是手动创建实例,也可以通过 Spring 注入。

Spring 修改版如下:

干掉了 INSTANCE,@Mapper 注解加入了 componentModel = "spring" 值。

/*** 微信公众号:Java技术栈* @author 栈长*/
@Mapper(componentModel = "spring")
public interface UserSpringStruct {@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")@Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))")@Mapping(source = "userExtDO.regSource", target = "registerSource")@Mapping(source = "userExtDO.favorite", target = "favorite")@Mapping(target = "memo", ignore = true)UserShowDTO toUserShowDTO(UserDO userDO);List<UserShowDTO> toUserShowDTOs(List<UserDO> userDOS);}

测试一下:

本文用到了 Spring Boot,所以这里就要用到 Spring Boot 的单元测试方法。Spring Boot 单元测试不懂的可以关注公众号:Java技术栈,在后台回复:boot,系列教程都整理好了。

/*** 微信公众号:Java技术栈* @author 栈长*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserSpringStructTest {@Autowiredprivate UserSpringStruct userSpringStruct;@Testpublic void test1() {UserExtDO userExtDO = new UserExtDO();userExtDO.setRegSource("公众号:Java技术栈");userExtDO.setFavorite("写代码");userExtDO.setSchool("社会大学");UserDO userDO = new UserDO();userDO.setName("栈长Spring");userDO.setSex(1);userDO.setAge(18);userDO.setBirthday(new Date());userDO.setPhone("18888888888");userDO.setMarried(true);userDO.setRegDate(new Date());userDO.setMemo("666");userDO.setUserExtDO(userExtDO);UserShowDTO userShowDTO = userSpringStruct.toUserShowDTO(userDO);System.out.println("=====单个对象映射=====");System.out.println(userShowDTO);List<UserDO> userDOs = new ArrayList<>();UserDO userDO2 = new UserDO();BeanUtils.copyProperties(userDO, userDO2);userDO2.setName("栈长Spring2");userDOs.add(userDO);userDOs.add(userDO2);List<UserShowDTO> userShowDTOs = userSpringStruct.toUserShowDTOs(userDOs);System.out.println("=====对象列表映射=====");userShowDTOs.forEach(System.out::println);}
}

如上所示,直接使用  @Autowired 注入就行,使用更方便。

输出结果:

图片

没毛病,稳如狗。

这篇关于干掉 BeanUtils!试试这款 Bean 自动映射工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/909740

相关文章

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

超强的截图工具:PixPin

你是否还在为寻找一款功能强大、操作简便的截图工具而烦恼?市面上那么多工具,常常让人无从选择。今天,想给大家安利一款神器——PixPin,一款真正解放双手的截图工具。 想象一下,你只需要按下快捷键就能轻松完成多种截图任务,还能快速编辑、标注甚至保存多种格式的图片。这款工具能满足这些需求吗? PixPin不仅支持全屏、窗口、区域截图等基础功能,它还可以进行延时截图,让你捕捉到每个关键画面。不仅如此

Python3 BeautifulSoup爬虫 POJ自动提交

POJ 提交代码采用Base64加密方式 import http.cookiejarimport loggingimport urllib.parseimport urllib.requestimport base64from bs4 import BeautifulSoupfrom submitcode import SubmitCodeclass SubmitPoj():de

PR曲线——一个更敏感的性能评估工具

在不均衡数据集的情况下,精确率-召回率(Precision-Recall, PR)曲线是一种非常有用的工具,因为它提供了比传统的ROC曲线更准确的性能评估。以下是PR曲线在不均衡数据情况下的一些作用: 关注少数类:在不均衡数据集中,少数类的样本数量远少于多数类。PR曲线通过关注少数类(通常是正类)的性能来弥补这一点,因为它直接评估模型在识别正类方面的能力。 精确率与召回率的平衡:精确率(Pr

husky 工具配置代码检查工作流:提交代码至仓库前做代码检查

提示:这篇博客以我前两篇博客作为先修知识,请大家先去看看我前两篇博客 博客指路:前端 ESlint 代码规范及修复代码规范错误-CSDN博客前端 Vue3 项目开发—— ESLint & prettier 配置代码风格-CSDN博客 husky 工具配置代码检查工作流的作用 在工作中,我们经常需要将写好的代码提交至代码仓库 但是由于程序员疏忽而将不规范的代码提交至仓库,显然是不合理的 所

10个好用的AI写作工具【亲测免费】

1. 光速写作 传送入口:http://u3v.cn/6hXWYa AI打工神器,一键生成文章&ppt 2. 讯飞写作 传送入口:http://m6z.cn/5ODiSw 3. 讯飞绘文 传送入口:https://turbodesk.xfyun.cn/?channelid=gj3 4. AI排版助手 传送入口:http://m6z.cn/6ppnPn 5. Kim

分享5款免费录屏的工具,搞定网课不怕错过!

虽然现在学生们不怎么上网课, 但是对于上班族或者是没有办法到学校参加课程的人来说,网课还是很重要的,今天,我就来跟大家分享一下我用过的几款录屏软件=,看看它们在录制网课时的表现如何。 福昕录屏大师 网址:https://www.foxitsoftware.cn/REC/ 这款软件给我的第一印象就是界面简洁,操作起来很直观。它支持全屏录制,也支持区域录制,这对于我这种需要同时录制PPT和老师讲