文章目录
- 工作原理
- 核心优势
- 为什么不使用 `BeanUtils`
- 使用步骤
- 添加依赖
- 定义实体类和VO类
- 定义映射接口
- 测试数据
- 参考
工作原理
基于 Java 的 JSR 269 规范,该规范允许在编译期处理注解,也就是 Java 注解处理器。MapStruct 通过定义的注解处理器,在编译期读取映射接口,并生成相应的实现类。这个过程中,它会解析接口中声明的映射方法,并创建对应的 getters 和 setters 调用
核心优势
- 零反射:生成的代码直接调用对象的
getter/setter
,性能接近手写代码。 - 编译时检查:映射错误(如字段不匹配)在编译时暴露,而非运行时。
- 灵活性:支持自定义转换逻辑、嵌套对象映射、集合转换等。
- 与 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 的核心注解,如
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 - 博客园