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 实战干货!

相关推荐
都叫我大帅哥1 天前
Java JJWT 完全指南:从入门到生产级实践
java
前端开发张小七1 天前
Java 学习笔记 · 第一课:基础语法与核心概念
java·后端
ThsPool1 天前
【ENVI二次开发学习整理 04】ENVI功能扩展与二次开发
java·前端·javascript
赤壁小虾1 天前
【渲染流水线】[逐片元阶段]-[透明度测试]以UnityURP为例
java·前端·数据库
青山木1 天前
Hot 100 --- 搜索插入位置
java·数据结构·算法·leetcode
Jul1en_1 天前
【Claude Code Compact】源码级别的学习上下文压缩
java·前端·学习·github·ai编程
摇滚侠1 天前
《SpringBoot 3:入门与应用实战》第 15 章 生产级特性 管理 Spring Boot 应用 阅读笔记
java·spring boot·笔记
XWalnut1 天前
SpringBoot快速入门
java·spring boot·后端
前端 贾公子1 天前
第06章:结构化输出 (上)
java·服务器·前端
念何架构之路1 天前
restartmanager-重启管理子系统
java·开发语言