前言
在 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 的处理原则是:能转则转,不转忽略。
-
源对象有、目标对象没有 → 自动忽略;
-
源对象没有、目标对象有 → 映射后为默认值(
null、0、false)。
如果希望目标对象有字段未映射时编译报错,可以配置:
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 包含 User 和 Goods),可以复用:
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?
-
性能优秀:编译期生成字节码,没有反射开销;
-
类型安全:编译期校验,出错早知道;
-
代码简洁:接口声明即可,无需手写转换逻辑;
-
灵活性高:支持字段名映射、类型转换、表达式计算、默认值等;
-
与 Spring 无缝集成 :支持
@Autowired注入其他 Mapper。
结语
MapStruct 是一个"小而美"的框架,它解决的是一个简单但高频的问题。在项目中使用 MapStruct,既能提升代码的可维护性,又不会引入复杂的运行时依赖。
如果你的项目中还在手写 set/get 转换,或者使用 BeanUtils 偷懒,不妨试试 MapStruct ------ 只需引入一个依赖,你的代码会变得更加优雅。
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,后续会持续输出更多 Java 实战干货!