1. 概述
本文将介绍如何在 MapStruct 框架中使用自定义映射器(Custom Mapper)。
✅ MapStruct 是一个用于 Java Bean 之间属性映射的代码生成库。它在编译期生成实现类,性能高且类型安全。
⚠️ 默认情况下,MapStruct 能处理字段名相同或相似的自动映射,但遇到复杂转换逻辑时就需要我们介入——这时候自定义映射器就派上用场了。
通过自定义映射方法,我们可以灵活控制字段转换过程,比如单位换算、枚举转换、字段拼接等场景。本文会以“英制单位转公制单位”为例,带你踩一遍常见坑。
2. Maven 依赖配置
要使用 MapStruct,首先需要引入核心依赖。以下是 pom.xml
中的关键配置:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.0.Beta1</version>
</dependency>
⚠️ 注意:MapStruct 是基于注解处理器(annotation processor)生成代码的,所以你还必须在 maven-compiler-plugin
中显式配置 annotationProcessorPaths
,否则编译后找不到生成的实现类!
<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</artifactId>
<version>1.6.0.Beta1</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
✅ 配置完成后,编译时 MapStruct 会在 target/generated-sources/annotations
目录下生成具体的 Mapper 实现类,方便调试和排查问题。
3. 自定义映射器的使用方式
当默认映射无法满足需求时(比如字段类型不一致、需要计算转换),就需要自定义映射逻辑。核心思路是:
- 定义一个转换方法(可以是静态方法)
- 告诉 MapStruct 在哪个字段映射时使用该方法
- MapStruct 在生成代码时自动调用该方法完成转换
我们以用户身体数据为例:前端传入英制单位(英寸 inch、磅 pound),后端需要转换为公制单位(厘米 cm、公斤 kg)用于 BMI 计算。
先定义两个 DTO 类:
public class UserBodyImperialValuesDTO {
private int inch;
private int pound;
// 构造方法、getter 和 setter 略
}
public class UserBodyValues {
private double kilogram;
private double centimeter;
// 构造方法、getter 和 setter 略
}
接下来介绍两种主流的自定义映射方式。
3.1 使用 @Named
注解标记方法
这是最简单粗暴的方式:直接用 @Named
给自定义转换方法起个名字,然后在 @Mapping
中通过 qualifiedByName
引用。
@Mapper
public interface UserBodyValuesMapper {
UserBodyValuesMapper INSTANCE = Mappers.getMapper(UserBodyValuesMapper.class);
@Mapping(source = "inch", target = "centimeter", qualifiedByName = "inchToCentimeter")
UserBodyValues userBodyValuesMapper(UserBodyImperialValuesDTO dto);
@Named("inchToCentimeter")
static double inchToCentimeter(int inch) {
return inch * 2.54;
}
}
📌 要点说明:
@Named("inchToCentimeter")
:为方法打标签,相当于注册了一个“可复用转换函数”qualifiedByName = "inchToCentimeter"
:告诉 MapStruct “inch → centimeter” 这个映射要用这个名字对应的方法- 方法必须是
public
或static
,且参数/返回值匹配
✅ 测试验证:
UserBodyImperialValuesDTO dto = new UserBodyImperialValuesDTO();
dto.setInch(10);
UserBodyValues obj = UserBodyValuesMapper.INSTANCE.userBodyValuesMapper(dto);
assertNotNull(obj);
assertEquals(25.4, obj.getCentimeter(), 0);
结果正确,10 英寸 = 25.4 厘米。
3.2 使用自定义注解(推荐)
虽然 @Named
写起来快,但字符串容易拼错,也不利于重构。更优雅的方式是定义一个专用注解,语义更清晰,IDE 支持更好。
步骤一:定义注解
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface PoundToKilogramMapper {
}
📌 说明:
@Qualifier
:这是 MapStruct 识别自定义映射器的关键注解,不能少@Target(METHOD)
:表示这个注解只能用在方法上@Retention(CLASS)
:保留到 class 文件即可,运行期不需要
步骤二:在转换方法上使用该注解
@PoundToKilogramMapper
static double poundToKilogram(int pound) {
return pound * 0.4535;
}
步骤三:在 Mapper 接口引用该注解
@Mapper
public interface UserBodyValuesMapper {
UserBodyValuesMapper INSTANCE = Mappers.getMapper(UserBodyValuesMapper.class);
@Mapping(source = "pound", target = "kilogram", qualifiedBy = PoundToKilogramMapper.class)
UserBodyValues userBodyValuesMapper(UserBodyImperialValuesDTO dto);
@PoundToKilogramMapper
static double poundToKilogram(int pound) {
return pound * 0.4535;
}
}
📌 关键区别:
- 使用
qualifiedBy = PoundToKilogramMapper.class
而非qualifiedByName
- 类型安全:编译器会检查注解是否存在,避免手误
- 可复用:多个 Mapper 接口都可以使用同一个注解
✅ 测试验证:
UserBodyImperialValuesDTO dto = new UserBodyImperialValuesDTO();
dto.setPound(100);
UserBodyValues obj = UserBodyValuesMapper.INSTANCE.userBodyValuesMapper(dto);
assertNotNull(obj);
assertEquals(45.35, obj.getKilogram(), 0);
100 磅 ≈ 45.35 公斤,结果正确。
4. 总结
本文通过实际案例展示了如何在 MapStruct 中实现自定义映射逻辑,重点包括:
✅ 两种方式对比:
方式 | 优点 | 缺点 | 推荐场景 |
---|---|---|---|
@Named + qualifiedByName |
上手快,适合临时转换 | 字符串易出错,难维护 | 快速原型、简单项目 |
自定义注解 + qualifiedBy |
类型安全、可复用、易重构 | 初期多写一个注解 | 中大型项目、团队协作 |
✅ 核心要点回顾:
- 自定义映射方法可以是静态方法,放在 Mapper 接口内或外部工具类中
- 必须通过
@Named
或自定义@Qualifier
注解暴露给 MapStruct - 编译期生成代码,务必配置
annotationProcessorPaths
- 推荐使用自定义注解方式,提升代码可维护性
所有示例代码已托管至 GitHub:https://github.com/example-user/mapstruct-custom-mapper-demo(mock 地址)