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

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.lryc.cn/news/2384047.html

相关文章:

  • 计算机网络——Session、Cookie 和 Token
  • 01-jenkins学习之旅-window-下载-安装-安装后设置向导
  • Spark,SparkSQL操作Mysql, 创建数据库和表
  • AttributeError: module ‘cv2.dnn‘ has no attribute ‘DictValue‘错误解决方法
  • HarmonyOS 鸿蒙应用开发基础:@Watch装饰器详解及与@Monitor装饰器对比分析
  • 机器人拖动示教控制
  • 免费开放试乘体验!苏州金龙自动驾驶巴士即将上线阳澄数谷
  • matlab加权核范数最小化图像去噪
  • docker容器暴露端口的作用
  • 每日Prompt:像素风格插画
  • Windows逆向工程提升之二进制分析工具:HEX查看与对比技术
  • Android10如何设置ro.debuggable=1?
  • 2024游戏安全白皮书:对抗激烈!PC游戏外挂功能数增长超149%,超85%移动外挂为定制挂(附获取方式)
  • 深度解析:Spark、Hive 与 Presto 的融合应用之道
  • 12kV 环保气体绝缘交流金属封闭开关设备现场交流耐压试验规范
  • 位图算法——判断唯一字符
  • HarmonyOS 鸿蒙应用开发基础:父组件调用子组件方法的几种实现方案对比
  • 复盘20250522
  • 【UE5】环形菜单教程
  • Athena 执行引擎:在线服务计算的效率王者
  • 飞桨paddle ‘ParallelEnv‘ object has no attribute ‘_device_id‘【已解决】
  • Bert预训练任务-MLM/NSP
  • 微信小程序之Promise-Promise初始用
  • 准备好,开始构建:由 Elasticsearch 向量数据库驱动的 Red Hat OpenShift AI 应用程序
  • spring的注入方式都有什么区别
  • RNN神经网络
  • Linux | 开机自启动设置多场景实现
  • 杨校老师竞赛课之青科赛GOC3-4年级组模拟题
  • 设计杂谈-工厂模式
  • SC3000智能相机-自动存图