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. 自定义映射器的使用方式

当默认映射无法满足需求时(比如字段类型不一致、需要计算转换),就需要自定义映射逻辑。核心思路是:

  1. 定义一个转换方法(可以是静态方法)
  2. 告诉 MapStruct 在哪个字段映射时使用该方法
  3. 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” 这个映射要用这个名字对应的方法
  • 方法必须是 publicstatic,且参数/返回值匹配

✅ 测试验证:

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 地址)


原始标题:Custom Mapper with MapStruct