MapStruct 从入门到精通:告别手写 Bean 转换的烦恼

前言

在 Java 分层架构开发中,我们几乎每天都在做同一件事:对象转换

  • Controller 层接收的 XXXDTO 要转成 Service 层的 XXXBO

  • Service 层处理完业务要转成 Repository 层的 XXXEntity

  • 返回给前端时,又要转成 XXXVO

这些转换逻辑重复、枯燥,而且容易出错。很多项目用 BeanUtils.copyProperties() 偷懒,但反射调用在性能敏感场景下始终是隐患。

直到我遇见了 MapStruct

一、MapStruct 是什么?

MapStruct 是一个 Java 对象映射框架 ,它的核心理念是:在编译期生成类型安全的映射代码

简单说,你只需要定义一个接口,声明好映射规则,MapStruct 就会在编译时自动帮你生成实现类,性能几乎等同于手写 getter/setter

它与 BeanUtils 的根本区别

对比项 BeanUtils(反射) MapStruct(编译期)
执行时机 运行时反射 编译期生成字节码
性能 较慢,适合低频场景 极快,接近手写
类型安全 运行时才报错 编译期就能发现
复杂映射 不支持 支持差异化字段名、类型转换等

二、快速入门:一分钟上手

1. Maven 依赖配置

xml

复制代码
<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>
            <configuration>
                <source>11</source>
                <target>11</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2. 定义 Mapper 接口

java

复制代码
@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    UserDTO toDTO(User user);
}

3. 使用

java

复制代码
User user = new User("张三", 25);
UserDTO dto = UserMapper.INSTANCE.toDTO(user);

编译后,MapStruct 会自动生成 UserMapperImpl 类,里面是一行行 setter/getter 赋值代码。

三、核心实战技巧

1. 字段名不同怎么办?

@Mapping 注解指定映射关系:

java

复制代码
@Mapper
public interface OrderMapper {
    @Mapping(source = "customerName", target = "userName")
    @Mapping(source = "orderAmount", target = "totalPrice")
    OrderDTO toDTO(Order order);
}

2. 字段数量不一致呢?

MapStruct 的处理原则是:能转则转,不转忽略

  • 源对象有、目标对象没有 → 自动忽略;

  • 源对象没有、目标对象有 → 映射后为默认值(null0false)。

如果希望目标对象有字段未映射时编译报错,可以配置:

java

复制代码
@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface StrictMapper {
    // 如果有字段没映射,编译期就会报错提醒
}

3. 日期格式转换

java

复制代码
@Mapping(source = "createTime", target = "createTimeStr", dateFormat = "yyyy-MM-dd HH:mm:ss")
UserDTO toDTO(User user);

4. 忽略某个字段

java

复制代码
@Mapping(target = "password", ignore = true)
UserDTO toDTO(User user);

5. 多个 Mapper 怎么组织?

业务实体/聚合根 划分,一个实体对应一个 Mapper:

java

复制代码
@Mapper
public interface UserMapper { ... }

@Mapper
public interface GoodsMapper { ... }

如果需要组合映射(如 OrderDTO 包含 UserGoods),可以复用:

java

复制代码
@Mapper(uses = {UserMapper.class, GoodsMapper.class})
public interface OrderMapper {
    OrderDTO toDTO(Order order);
}

四、与 Lombok 搭配的避坑指南

很多项目同时使用 Lombok 来减少样板代码,两者配合使用时有一个关键顺序问题:Lombok 的注解处理器必须先于 MapStruct 执行

原因是 MapStruct 生成映射代码时需要调用 Lombok 生成的 getter/setter 方法。如果 MapStruct 先执行,Lombok 还没生成这些方法,编译就会失败。

正确配置方式

xml

复制代码
<plugin>
    <groupId>org.apache.maven.compiler</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <!-- 1. Lombok 必须放在最前面 -->
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.32</version>
            </path>
            <!-- 2. 绑定依赖(Lombok 1.18.16+ 建议添加) -->
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok-mapstruct-binding</artifactId>
                <version>0.2.0</version>
            </path>
            <!-- 3. MapStruct 处理器 -->
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.6.3</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

IDEA 额外检查

确保勾选:Settings > Build, Execution, Deployment > Compiler > Annotation Processors > Enable annotation processing

五、为什么值得用 MapStruct?

  1. 性能优秀:编译期生成字节码,没有反射开销;

  2. 类型安全:编译期校验,出错早知道;

  3. 代码简洁:接口声明即可,无需手写转换逻辑;

  4. 灵活性高:支持字段名映射、类型转换、表达式计算、默认值等;

  5. 与 Spring 无缝集成 :支持 @Autowired 注入其他 Mapper。

结语

MapStruct 是一个"小而美"的框架,它解决的是一个简单但高频的问题。在项目中使用 MapStruct,既能提升代码的可维护性,又不会引入复杂的运行时依赖。

如果你的项目中还在手写 set/get 转换,或者使用 BeanUtils 偷懒,不妨试试 MapStruct ------ 只需引入一个依赖,你的代码会变得更加优雅。


如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,后续会持续输出更多 Java 实战干货!

相关推荐
智慧物业老杨7 小时前
物业数字化落地思考:真正的转型,是底层数据秩序的重构
java·大数据·人工智能·微服务·系统架构
星空10 小时前
金蝶苍穹build.gradle配置
java·build.gradle
步行cgn13 小时前
Spring 基于 XML 的自动装配:byName 详解
java·后端·spring
大帅点兵14 小时前
Apache Ant 1.9.9 完整讲解
java
LuTshoes14 小时前
spring ai 实战 手搓 PlaneExecuteAgent
java·人工智能·spring·ai
这料鬼有毒14 小时前
二刷hot100-73.矩阵置零
java
caoerzhong14 小时前
JeeWMS 开源仓库管理系统二次开发与接口对接实战:Java WMS 如何与 ERP、MES 和自动化设备打通
java·开源
长谷深风11114 小时前
评测AI Agent:三种裁判各司其职
java·大数据·开发语言·人工智能·ai agent
边境悍匪14 小时前
蜗牛学苑 Java 智能体学习 Day42|项目周开发技术汇总 1 思维导图复盘
java·开发语言·spring boot·学习·spring
槑有烦恼15 小时前
Java 字符串 & List集合 高频易错真题复盘(含全部错题+修正)
java