当前位置: 首页 > ai >正文

SpringBoot 对象转换 MapStruct

文章目录

    • 工作原理
    • 核心优势
    • 为什么不使用 `BeanUtils`
    • 使用步骤
      • 添加依赖
      • 定义实体类和VO类
      • 定义映射接口
      • 测试数据
    • 参考

工作原理

基于 Java 的 JSR 269 规范,该规范允许在编译期处理注解,也就是 Java 注解处理器。MapStruct 通过定义的注解处理器,在编译期读取映射接口,并生成相应的实现类。这个过程中,它会解析接口中声明的映射方法,并创建对应的 getters 和 setters 调用

核心优势

  1. 零反射:生成的代码直接调用对象的 getter/setter,性能接近手写代码。
  2. 编译时检查:映射错误(如字段不匹配)在编译时暴露,而非运行时。
  3. 灵活性:支持自定义转换逻辑、嵌套对象映射、集合转换等。
  4. 与 IDE 兼容:生成的代码可调试,便于跟踪问题。

为什么不使用 BeanUtils

在高并发的场景中,性能是最为重要的,BeanUtils 虽然可以快速完成 JavaBean 之间的转换,但是底层逻辑是基于反射实现的,这样会导致在高并发场景中性能下降,这时候最高效的处理办法就是手动的 getter/setter,但是要大量处理这些可重复的操作会浪费大量时间,因此可以使用 MapStruct 解决

区别:

  • 编译时生成代码 vs 运行时反射MapStruct生成的映射代码是在编译时生成的,而BeanUtils则是在运行时使用反射机制实现转换
  • 性能和可扩展性:由于 MapStruct 生成的代码是类型安全的,因此可以比使用反射更加高效和可靠。同时,MapStruct 还能够自定义转换逻辑并支持扩展,使得它更加灵活和可扩展。
  • 集成方式:MapStruct 可以无缝集成到 Spring 中,也可以与其他 IoC 容器结合使用;而 BeanUtils 是 Spring 框架自带的工具类。
  • 映射规则的定义方式:MapStruct 使用基于注解的方式在源代码中定义映射规则,而 BeanUtils 则需要手动编写复杂的转换方法。

使用步骤

添加依赖

pom.xml 中添加依赖

<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.5.5.Final</version>
</dependency>
<!-- 注解处理器 -->
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.5.5.Final</version><scope>provided</scope>
</dependency>
  • mapstruct 依赖
    • 提供了 MapStruct 的核心注解,如 @Mapper@Mapping,用于定义映射接口
    • 在运行时,如果使用默认的组件模型(default),还需要依赖 Mappers.getMapper(…) 方法来获取映射器实例
  • mapstruct-processor 依赖
    • 注解处理器(annotation processor),在编译阶段扫描带有 MapStruct 注解的接口,并生成对应的实现类
    • 不会在运行时参与应用程序的执行,因此通常不需要在运行时包含此依赖

根据 mapstruct-processor 依赖的定义,其实不应该将该依赖放在 dependencies 标签中,而是将 mapstruct-processor 作为注解处理器添加到 maven-compiler-plugin 的 annotationProcessorPaths 中,这也是官方推荐处理

...
<properties><org.mapstruct.version>1.6.3</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><target>1.8</target><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version></path></annotationProcessorPaths></configuration></plugin></plugins>
</build>
...

如果是在已经有 Lombok 依赖的项目中加入 mapstruct 依赖需要注意这两个注解处理器的配置,不然在 mvn install 等命令执行时可能会发生关于 Lombok 依赖相关的错误

Lombok 1.18.16 引入了一个破坏性更改(变更日志)。必须添加额外的注解处理器 lombok-mapstruct-binding (Maven),否则 MapStruct 将无法与 Lombok 兼容。

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.13.0</version><configuration><source>17</source><target>17</target><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${mapstruct.version}</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></path><!-- Lombok 版本从 1.18.16 开始必须添加,其他版本为可选 --><!-- Lombok 与 MapStruct 的绑定处理器 --><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>0.2.0</version></path></annotationProcessorPaths></configuration></plugin></plugins>
</build>

定义实体类和VO类

// 实体类
@Data  
@Accessors(chain = true)  
public class Student implements Serializable {  private Long  id;  private String name;  private Integer age;  private String studentNo;  }// VO类
@Data  
@Accessors(chain = true)  
public class StudentVO implements Serializable {  private Long voId;  private String voName;  private Integer voAge;  private String voStudentNo;  }

定义映射接口

定义抽象接口

@MapperConfig  
public interface IMapping<SOURCE, TARGET> {  TARGET sourceToTarget(SOURCE source);  // 反向映射(需配置反向方法)@InheritInverseConfiguration(name = "sourceToTarget")  SOURCE targetToSource(TARGET target);  @InheritConfiguration(name = "sourceToTarget")  List<TARGET> sourceToTarget(List<SOURCE> sourceList);  @InheritConfiguration(name = "sourceToTarget")  List<SOURCE> targetToSource(List<TARGET> targetList);  List<TARGET> sourceToTarget(Stream<SOURCE> stream);  List<SOURCE> targetToSource(Stream<TARGET> stream);  }

定义映射接口,@Mapper(componentModel = "spring")。默认情况下,mapstruct 生成的 Mapper 实现类不会被 Spring 容器管理。如果不指定 componentModel,需要通过 StudentMapping mapper = Mappers.getMapper(StudentMapping.class) 手动获取 Mapper 实例。

通过设置 componentModel = "spring"mapstruct 会在生成的实现类上添加 @Component 注解,使其成为 Spring 管理的 Bean,从而可以在其他组件中通过依赖注入方式使用。因此,StudentMapping 接口通过 @Mapper(componentModel = "spring") 注解,其实现类被注册为 SpringBean,可以通过构造函数注入方式使用该 Mapper

@Mapper(componentModel = "spring")  
public interface StudentMapping extends IMapping<Student, StudentVO>{  @Override  @Mapping(source = "id", target = "voId")  @Mapping(source = "name", target = "voName")  @Mapping(source = "age", target = "voAge")  @Mapping(source = "studentNo", target = "voStudentNo")  StudentVO sourceToTarget(Student student);  @Override  List<StudentVO> sourceToTarget(List<Student> students);  
}

测试数据

默认情况下,Spring 在实例化测试类时使用无参构造函数,并通过字段注入(@Autowired)方式注入依赖。而使用 @TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL) 注解后,Spring 会尝试使用带参数的构造函数,并通过构造函数注入方式自动注入所需的依赖,也就是可以使用 Lombok 依赖中的 @RequiredArgsConstructor 注解

@SpringBootTest  
@RequiredArgsConstructor  
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)  
public class StudentMapStructTest {  private final StudentMapping studentMapping;  @Test  public void test() {  Student student = new Student();  student.setId(1L);  student.setName("Mayer");  student.setAge(18);  student.setStudentNo("20250001");  StudentVO studentVO = studentMapping.sourceToTarget(student);  System.out.println(studentVO);  System.out.println(studentMapping.targetToSource(studentVO));  }  }

打印结果

StudentVO(voId=1, voName=Mayer, voAge=18, voStudentNo=20250001)
Student(id=1, name=Mayer, age=18, studentNo=20250001)

参考

  • Java-Mapstruct 实践 | 无垠之境
  • Java Review - MapStruct 全掌握:8 个案例探究高效快捷的 Java 对象映射 - CharyGao - 博客园
  • 【MapStruct】还在用BeanUtils?不如试试MapStruct - CharyGao - 博客园
http://www.xdnf.cn/news/8157.html

相关文章:

  • 《函数指针数组:创建与使用指南》
  • 【T2I】Controllable Generation with Text-to-ImageDiffusion Models: A Survey
  • 嵌入式学习笔记 D25 :标准i/o操作(2)、文件i/o
  • 2025年5月通信科技领域周报(5.12-5.18):6G太赫兹技术商用突破 空天地一体化网络进入规模部署期
  • Windows解除占用(解除文件占用、解除目录占用)查看文件进程(查看父进程、查看子进程、查看父子进程)占用文件占用、占用目录占用
  • 纳斯达克与标普500的技术博弈:解析美股交易系统的低延迟与高安全解决方案
  • 基于SpringBoot的动漫交流与推荐平台-036
  • 【学习笔记】计算机操作系统(五)—— 虚拟存储器
  • 数据库5——审计及触发器
  • 模拟地和数字地的连接方式
  • Java中的大根堆与小根堆
  • 无人机避障——深蓝学院浙大Ego-Planner规划部分
  • 工具看点 | 澳鹏多模态标注工具:构建AI认知的语义桥梁
  • 第四十五节:目标检测与跟踪-Meanshift/Camshift 算法
  • MCP Server Resource 开发学习文档
  • 记一次奇葩的错误,uniapp @tap点击失效
  • Nockchain项目部署教程
  • 从连接中枢到终端接入——解析工业无线AP与客户端的协同之道
  • 安装部署配置jenkins
  • Nginx 1.25.4交叉编译问题:编译器路径与aclocal.m4错误解决方案
  • wifi 如果检查失败,UI 就会出现延迟或缺失打勾的现象。
  • linux中部署jdk,开机自启动jdk以及linux中java开机自启某个jar包文件
  • 算法第26天 | 贪心算法、455.分发饼干、376. 摆动序列、 53. 最大子序和
  • 如何在Java中进行PDF合并
  • 软考 系统架构设计师系列知识点之杂项集萃(69)
  • Linux Shell编程(五)
  • 【鸿蒙开发】Hi3861学习笔记-超声波测距
  • HTB-Titanic
  • 多模态大语言模型arxiv论文略读(八十八)
  • LeetCode面试经典150题梳理