百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

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

ccwgpt 2024-10-13 01:28 38 浏览 0 评论

简介

在业务项目的开发中,我们经常需要将 Java 对象进行转换,比如从将外部微服务得到的对象转换为本域的业务对象 domain object,将 domain object 转为数据持久层的 data object,将 domain object 转换为 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 对象
 */
 @Before
 public void before() {
 Role role = new Role(2L, "administrator", "超级管理员");
 user = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);
 }
 /**
 * 模拟把 user 对象转换成 UserRoleDto 对象
 */
 @Test
 public 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.java、Role.java、UserRoleDto.java。然后新建一个 UserRoleMapper.java,这个来用来定义 User.java、Role.java和UserRoleDto.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对象
 */
 @Test
 public void test2() {
 UserRoleDto userRoleDto = UserRoleMapper.INSTANCES.toUserRoleDto(user);
 System.out.println(userRoleDto);
 }

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

核心是这一句 :java.lang.ClassNotFoundException: Cannot find implementation for top.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 提供了一个插件来解决这个问题,同时还提供代码自动提示、点击跳转到实现等功能。

关于插件的更多信息,参见 https://yq.aliyun.com/go/articleRenderRedirect?url=http%3A%2F%2Fmapstruct.org%2Fnews%2F2017-09-19-announcing-mapstruct-idea%2F

安装插件的过程

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

代码独立

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

相关推荐

一个基于.Net Core遵循Clean Architecture原则开源架构

今天给大家推荐一个遵循CleanArchitecture原则开源架构。项目简介这是基于Asp.netCore6开发的,遵循CleanArchitecture原则,可以高效、快速地构建基于Ra...

AI写代码翻车无数次,我发现只要提前做好这3步,bug立减80%

写十万行全是bug之后终于找到方法了开发"提示词管理助手"新版本那会儿,我差点被bug整崩溃。刚开始两周,全靠AI改代码架构,结果十万行程序漏洞百出。本来以为AI说没问题就稳了,结果...

OneCode低代码平台的事件驱动设计:架构解析与实践

引言:低代码平台的事件驱动范式在现代软件开发中,事件驱动架构(EDA)已成为构建灵活、松耦合系统的核心范式。OneCode低代码平台通过创新性的注解驱动设计,将事件驱动理念深度融入平台架构,实现了业务...

国内大厂AI插件评测:根据UI图生成Vue前端代码

在IDEA中安装大厂的AI插件,打开ruoyi增强项目:yudao-ui-admin-vue31.CodeBuddy插件登录腾讯的CodeBuddy后,大模型选择deepseek-v3,输入提示语:...

AI+低代码技术揭秘(二):核心架构

本文档介绍了为VTJ低代码平台提供支持的基本架构组件,包括Engine编排层、Provider服务系统、数据模型和代码生成管道。有关UI组件库和widget系统的信息,请参阅UI...

GitDiagram用AI把代码库变成可视化架构图

这是一个名为gitdiagram的开源工具,可将GitHub仓库实时转换为交互式架构图,帮助开发者快速理解代码结构。核心功能一键可视化:替换GitHubURL中的"hub...

30天自制操作系统:第六天:代码架构整理与中断处理

1.拆开bootpack.c文件。根据设计模式将对应的功能封装成独立的文件。2.初始化pic:pic(可编程中断控制器):在设计上,cpu单独只能处理一个中断。而pic是将8个中断信号集合成一个中断...

AI写代码越帮越忙?2025年研究揭露惊人真相

近年来,AI工具如雨后春笋般涌现,许多人开始幻想程序员的未来就是“对着AI说几句话”,就能轻松写出完美的代码。然而,2025年的一项最新研究却颠覆了这一期待,揭示了一个令人意外的结果。研究邀请了16位...

一键理解开源项目:两个自动生成GitHub代码架构图与说明书工具

一、GitDiagram可以一键生成github代码仓库的架构图如果想要可视化github开源项目:https://github.com/luler/reflex_ai_fast,也可以直接把域名替换...

5分钟掌握 c# 网络通讯架构及代码示例

以下是C#网络通讯架构的核心要点及代码示例,按协议类型分类整理:一、TCP协议(可靠连接)1.同步通信//服务器端usingSystem.Net.Sockets;usingTcpListene...

从复杂到优雅:用建造者和责任链重塑代码架构

引用设计模式是软件开发中的重要工具,它为解决常见问题提供了标准化的解决方案,提高了代码的可维护性和可扩展性,提升了开发效率,促进了团队协作,提高了软件质量,并帮助开发者更好地适应需求变化。通过学习和应...

低代码开发当道,我还需要学习LangChain这些框架吗?| IT杂谈

专注LLM深度应用,关注我不迷路前两天有位兄弟问了个问题:当然我很能理解这位朋友的担忧:期望效率最大化,时间用在刀刃上,“不要重新发明轮子”嘛。铺天盖地的AI信息轰炸与概念炒作,很容易让人浮躁与迷茫。...

框架设计并不是简单粗暴地写代码,而是要先弄清逻辑

3.框架设计3.框架设计本节我们要开发一个UI框架,底层以白鹭引擎为例。框架设计的第一步并不是直接撸代码,而是先想清楚设计思想,抽象。一个一个的UI窗口是独立的吗?不是的,...

大佬用 Avalonia 框架开发的 C# 代码 IDE

AvalonStudioAvalonStudio是一个开源的跨平台的开发编辑器(IDE),AvalonStudio的目标是成为一个功能齐全,并且可以让开发者快速使用的IDE,提高开发的生产力。A...

轻量级框架Lagent 仅需20行代码即可构建自己的智能代理

站长之家(ChinaZ.com)8月30日消息:Lagent是一个专注于基于LLM模型的代理开发的轻量级框架。它的设计旨在简化和提高这种模型下代理的开发效率。LLM模型是一种强大的工具,可以...

取消回复欢迎 发表评论: