1. 项目概述:为什么要在Eclipse里折腾MapStruct?
如果你是一个长期在Eclipse环境下进行Java后端开发的工程师,最近肯定没少被“对象映射”这件事烦心。尤其是在微服务架构和前后端分离成为主流的今天,一个业务对象(比如User)在数据库层、服务层、接口层、甚至消息队列里,可能对应着UserEntity、UserDTO、UserVO、UserMessage等好几种形态。手动写getter和setter来在这些对象之间复制属性,不仅代码冗长、容易出错,而且一旦字段增减或改名,维护起来简直就是灾难。这时候,一个高效的映射工具就成了刚需。
MapStruct正是为了解决这个问题而生的。它是一个基于注解处理器(Annotation Processor)的代码生成器,能在编译期为你生成类型安全、高性能的映射代码。简单来说,你只需要定义一个接口,用注解描述一下映射规则,MapStruct就会在编译时自动生成这个接口的实现类,里面包含了完整的、手写级别的属性拷贝代码。其性能几乎与手写代码无异,远高于使用反射的BeanUtils等工具。
那么,为什么特别强调“在Eclipse中”呢?原因有几个。首先,Eclipse作为一个历史悠久、用户基数庞大的IDE,尤其在传统企业和教育领域,依然是主力开发工具。其次,MapStruct的核心机制——注解处理器,在不同IDE中的配置和集成方式有细微差别。在IntelliJ IDEA中,由于其构建系统(特别是对Maven和Gradle的深度集成)对注解处理器的支持相对“自动化”,配置可能更顺畅。而Eclipse的构建过程(特别是基于其自有构建器时)需要开发者进行更明确的手动配置,才能确保MapStruct的代码生成器被正确触发并集成到编译流程中。很多开发者在这里踩坑,导致映射接口生成了,但实现类没出现,或者编译报错。因此,专门梳理在Eclipse这一特定环境下的完整实践路径,具有很强的现实意义。本文将带你从零开始,在Eclipse中完成MapStruct的集成、配置、开发、调试全流程,并分享我趟过的那些坑和总结出的最佳实践。
2. 环境准备与项目搭建
在开始编写映射代码之前,一个正确配置的工程环境是基石。这一步没做好,后面所有工作都可能白费。
2.1 基础环境确认
首先,确保你的本地环境符合要求:
- JDK版本:MapStruct 1.4.x及以上版本需要JDK 8或更高版本。推荐使用JDK 11或JDK 17这些LTS版本。你可以在Eclipse中通过
Window->Preferences->Java->Installed JREs查看和配置。 - Eclipse版本:建议使用较新的Eclipse IDE for Enterprise Java and Web Developers版本。老版本的Eclipse对注解处理器的支持可能不完善。我使用的是
Eclipse 2023-12,它内置了对现代Java和构建工具的良好支持。 - 构建工具:Maven或Gradle任选其一。MapStruct对两者都有完善的支持。考虑到Eclipse对Maven的原生集成非常成熟,本文将以Maven为例进行演示,但核心原理对Gradle同样适用。
2.2 创建Maven项目并引入依赖
在Eclipse中,通过File->New->Other...->Maven->Maven Project创建一个简单的Maven项目。在pom.xml中,我们需要添加MapStruct的核心依赖和注解处理器插件。
核心依赖:org.mapstruct:mapstruct包含了运行所需的注解(如@Mapper),而org.mapstruct:mapstruct-processor则是代码生成器本身。注意,在Maven中,处理器依赖的scope通常设置为provided,因为它只在编译阶段需要,不应打包到最终的JAR或WAR中。
关键插件配置:为了让MapStruct处理器在Eclipse的增量编译和Maven命令行编译中都能工作,我们需要在pom.xml的<build>-><plugins>部分配置maven-compiler-plugin。这里有一个至关重要的细节:必须将mapstruct-processor的路径明确传递给编译器插件。很多教程省略了这一步,导致在Eclipse中代码生成失败。
下面是一个完整的pom.xml依赖和插件配置示例:
<properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <org.mapstruct.version>1.5.5.Final</org.mapstruct.version> <!-- 使用当前稳定版本 --> </properties> <dependencies> <!-- MapStruct 核心注解依赖 --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <!-- 其他项目依赖,如Lombok(如果使用) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> <annotationProcessorPaths> <!-- MapStruct 注解处理器路径 --> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> <!-- 如果同时使用 Lombok,必须将其处理器也加入,且顺序在MapStruct之前 --> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> </path> <!-- 其他注解处理器,如 QueryDSL 等 --> </annotationProcessorPaths> <!-- 此配置项有时对解决Eclipse问题有帮助 --> <compilerArgs> <arg>-Amapstruct.defaultComponentModel=spring</arg> <!-- 示例:生成Spring组件 --> </compilerArgs> </configuration> </plugin> </plugins> </build>注意:如果你同时使用了Lombok,
annotationProcessorPaths中必须包含Lombok的处理器,并且建议将其放在MapStruct处理器之前。这是因为MapStruct在生成代码时需要读取编译后的类信息,而Lombok会先修改类的结构(生成getter/setter等)。如果顺序反了,MapStruct可能找不到应有的getter方法而报错。
2.3 配置Eclipse的注解处理器
这是让MapStruct在Eclipse中“活”起来最关键的一步。仅仅有正确的pom.xml还不够,必须让Eclipse自身的Java编译器也启用注解处理。
- 在Eclipse的
Package Explorer或Project Explorer中,右键点击你的项目,选择Properties。 - 在左侧菜单中找到
Java Compiler->Annotation Processing。 - 勾选
Enable annotation processing。这个总开关打开了,Eclipse才会在项目构建时调用注解处理器。 - 切换到
Annotation Processing->Factory Path选项卡。这是指定处理器JAR包的地方。 - 点击
Add JARs...按钮,你需要添加两个JAR(如果你的项目依赖了它们):mapstruct-processor-{version}.jar:这个JAR通常位于你的本地Maven仓库中,路径类似~/.m2/repository/org/mapstruct/mapstruct-processor/{version}/。lombok.jar:如果你用了Lombok,同样需要添加它的JAR。Lombok的JAR通常位于你安装的Lombok插件目录下,或者也可以从Maven仓库中添加。
- 添加完成后,确保这两个JAR被勾选上。
- 点击
Apply and Close。
实操心得:完成这一步后,我强烈建议你对项目进行一次彻底的清理和重建。右键项目 ->Clean...-> 选择你的项目 ->Clean。然后,右键项目 ->Maven->Update Project...(快捷键Alt+F5),勾选Force Update of Snapshots/Releases,点击OK。这个组合拳能解决90%因Eclipse缓存或配置未同步导致的MapStruct代码生成失败问题。
3. MapStruct核心概念与第一个映射器
环境配好了,我们来动手写第一个映射器,并理解其中的核心概念。
3.1 定义源对象和目标对象
假设我们有一个从数据库查询出来的用户实体UserEntity,和一个用于API响应的视图对象UserVO。
// 源对象:用户实体 (通常对应数据库表) public class UserEntity { private Long id; private String username; private String email; private String passwordHash; // 密码哈希,不应暴露给前端 private LocalDateTime createTime; // ... 省略 getters and setters (可使用Lombok @Data) } // 目标对象:用户视图对象 public class UserVO { private Long userId; private String name; private String emailAddress; private String registrationTime; // ... 省略 getters and setters }注意两个类字段名的差异:idvsuserId,usernamevsname,emailvsemailAddress,createTimevsregistrationTime。同时,passwordHash字段在UserVO中不存在,我们不希望它被映射过去。
3.2 创建映射器接口
现在,我们创建一个映射器接口来定义转换规则。在Eclipse中,新建一个接口,通常放在mapper或converter包下。
import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Mapper // 1. 标记这是一个MapStruct映射器 public interface UserMapper { // 2. 获取映射器实例的便捷方式(当不使用依赖注入时) UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); // 3. 定义映射方法:从 UserEntity 到 UserVO @Mapping(source = "id", target = "userId") // 字段名不同,指定对应关系 @Mapping(source = "username", target = "name") @Mapping(source = "email", target = "emailAddress") @Mapping(source = "createTime", target = "registrationTime", dateFormat = "yyyy-MM-dd HH:mm:ss") @Mapping(target = "passwordHash", ignore = true) // 忽略源字段,不映射 UserVO toVO(UserEntity user); // 4. 反向映射(可选) @Mapping(source = "userId", target = "id") @Mapping(source = "name", target = "username") @Mapping(source = "emailAddress", target = "email") // 注意:String到LocalDateTime的转换需要自定义方法,这里先忽略或后面处理 @Mapping(target = "createTime", ignore = true) @Mapping(target = "passwordHash", ignore = true) UserEntity toEntity(UserVO userVO); // 5. 自定义类型转换方法(如果需要) default String formatLocalDateTime(LocalDateTime dateTime) { if (dateTime == null) { return null; } return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } }代码解析与核心概念:
@Mapper注解:这是MapStruct的根注解,标记一个接口是映射器。它还可以配置一些全局属性,比如componentModel(后面会讲)。INSTANCE:这是一种简单的使用方式,通过Mappers.getMapper获取映射器实现类的单例实例。适用于不整合Spring等DI框架的小型应用或测试。@Mapping注解:这是最常用的注解,用于在方法级别定义字段映射规则。source:源对象的属性名。target:目标对象的属性名。dateFormat:当源和目标属性是日期/时间类型与字符串之间转换时,指定格式。ignore:设为true时,MapStruct将不会映射该目标字段,即使源对象中有同名字段。
- 反向映射:你可以轻松地定义反向映射方法。MapStruct很智能,但复杂的反向映射(如格式化的字符串转回
LocalDateTime)可能需要辅助方法。 - 自定义方法:你可以在接口中定义
default方法或静态方法,来处理MapStruct无法自动完成的复杂转换逻辑。例如上面的formatLocalDateTime方法,它会被映射器实现类自动调用,用于将LocalDateTime类型的createTime转换为String类型的registrationTime。
3.3 触发代码生成与查看结果
保存接口文件后,如果Eclipse的注解处理配置正确,MapStruct处理器会自动工作。你不需要手动执行任何命令。生成的代码在哪里呢?
在Eclipse中,生成的源代码默认放在一个特殊的目录下。你需要展开项目,找到target/generated-sources/annotations目录(Maven项目)。在这个目录下,你会找到一个与你的接口同包的类,例如com/yourproject/mapper/UserMapperImpl.java。
打开这个UserMapperImpl.java文件,你会看到MapStruct为你生成的、非常直观的代码:
// 生成代码示例(简化版) public class UserMapperImpl implements UserMapper { @Override public UserVO toVO(UserEntity user) { if (user == null) { return null; } UserVO userVO = new UserVO(); userVO.setUserId(user.getId()); userVO.setName(user.getUsername()); userVO.setEmailAddress(user.getEmail()); if (user.getCreateTime() != null) { userVO.setRegistrationTime(formatLocalDateTime(user.getCreateTime())); // 调用了自定义方法 } // passwordHash 被忽略,没有映射代码 return userVO; } // ... 其他方法实现 }这就是MapStruct的魅力:生成的代码就像优秀程序员手写的一样,高效、安全(有null检查)、可读。你可以在Eclipse中像查看普通源码一样查看、调试这些生成代码。
注意事项:有时你可能在
target/generated-sources/annotations下看不到生成的类。请按以下步骤排查:
- 确认
pom.xml中的annotationProcessorPaths配置正确。- 确认Eclipse的
Annotation Processing已启用且Factory Path已添加处理器JAR。- 执行项目
Clean和Maven -> Update Project。- 尝试在命令行进入项目根目录,执行
mvn compile。如果命令行能生成,但Eclipse里不显示,可能是Eclipse的源码路径问题。右键项目 ->Properties->Java Build Path->Source,点击Add Folder...,勾选target/generated-sources/annotations,然后Apply and Close。之后再次Clean项目。
4. 高级映射技巧与复杂场景处理
掌握了基础映射后,我们来看看MapStruct如何处理更复杂的场景,这些才是体现其价值的地方。
4.1 处理嵌套对象与集合映射
现实中的对象关系很少是扁平的。例如,一个OrderEntity可能包含一个UserEntity,而OrderVO包含一个UserVO。
// 源对象 public class OrderEntity { private String orderId; private BigDecimal amount; private UserEntity buyer; // 嵌套对象 private List<OrderItemEntity> items; // 嵌套集合 } // 目标对象 public class OrderVO { private String orderSn; private String totalAmount; private UserVO purchaser; // 需要将UserEntity映射为UserVO private List<OrderItemVO> itemList; // 需要将List<OrderItemEntity>映射为List<OrderItemVO> }对于这种情况,MapStruct的表现非常出色。只要为嵌套的类型(UserEntity->UserVO,OrderItemEntity->OrderItemVO)定义了对应的映射器,MapStruct在映射Order时就会自动调用它们。
@Mapper(uses = {UserMapper.class, OrderItemMapper.class}) // 声明需要使用的其他映射器 public interface OrderMapper { @Mapping(source = "orderId", target = "orderSn") @Mapping(source = "amount", target = "totalAmount", numberFormat = "#0.00") // 数字格式化 @Mapping(source = "buyer", target = "purchaser") @Mapping(source = "items", target = "itemList") OrderVO toVO(OrderEntity order); // 集合映射会自动遍历并调用元素级别的映射方法 }关键在于@Mapper(uses = {...})。它告诉MapStruct:“在处理OrderMapper的映射时,如果你遇到UserEntity或OrderItemEntity,请去调用我指定的那个映射器里的方法。” 这样,复杂的嵌套映射就被优雅地分解了。
4.2 类型转换与自定义映射方法
MapStruct内置了许多默认的类型转换,比如基本类型与其包装类、String与基本类型、Date与String(需指定格式)等。但对于更特殊的转换,我们需要自定义方法。
场景一:枚举与字符串的智能转换假设源对象中状态是枚举OrderStatus. PAID,而目标对象中需要字符串"已支付"。
public enum OrderStatus { UNPAID("未支付"), PAID("已支付"), SHIPPED("已发货"); private final String desc; OrderStatus(String desc) { this.desc = desc; } public String getDesc() { return desc; } } // 在映射器接口中 @Mapping(source = "status", target = "statusText") OrderVO toVO(OrderEntity order); // MapStruct会自动尝试调用`status.getDesc()`来获取字符串!因为它遵循Bean规范。 // 如果getter方法名不是`getDesc`,比如是`getDescription()`,则需用@Mapping指定qualifiedByName。场景二:多个源参数映射到一个目标对象这在聚合数据时非常有用。例如,根据UserEntity和UserProfileEntity来构建一个完整的UserDetailVO。
@Mapping(source = "user.id", target = "userId") @Mapping(source = "user.username", target = "userName") @Mapping(source = "profile.avatarUrl", target = "avatar") @Mapping(source = "profile.bio", target = "introduction") UserDetailVO toDetailVO(UserEntity user, UserProfileEntity profile); // MapStruct会生成一个方法,接受两个参数,并从中提取所需属性来构造目标对象。场景三:使用@BeforeMapping和@AfterMapping进行映射前后处理这两个注解允许你在自动生成的映射代码执行前后插入自定义逻辑。
@Mapper public interface ProductMapper { ProductVO toVO(ProductEntity product); @BeforeMapping // 在映射开始前执行 default void validateEntity(ProductEntity product) { if (product == null || product.getId() == null) { throw new IllegalArgumentException("Invalid product entity"); } } @AfterMapping // 在映射完成后执行 default void calculateDiscount(ProductEntity product, @MappingTarget ProductVO vo) { // @MappingTarget 注解表示正在被构建的目标对象 if (product.getPromotion() != null) { BigDecimal finalPrice = product.getPrice().multiply( BigDecimal.ONE.subtract(product.getPromotion().getDiscountRate()) ); vo.setFinalPrice(finalPrice.setScale(2, RoundingMode.HALF_UP)); vo.setHasDiscount(true); } else { vo.setFinalPrice(product.getPrice()); vo.setHasDiscount(false); } } }@AfterMapping特别适合处理那些依赖多个源字段、需要计算才能得出的目标字段,它能让你保持映射接口的声明式风格,同时不失去灵活性。
4.3 与Spring框架集成(Component Model)
在Spring项目中,我们通常希望映射器像其他Service一样,被Spring容器管理,并能自动注入(@Autowired)。MapStruct通过componentModel参数完美支持这一点。
修改你的@Mapper注解:
import org.mapstruct.Mapper; // 注意:对于MapStruct 1.5.x,通常使用 `spring`。早期版本可能是 `cdi`, `jsr330`等。 @Mapper(componentModel = "spring") // 关键在这里 public interface UserMapper { // 不再需要 INSTANCE 静态实例 UserVO toVO(UserEntity user); }当componentModel = "spring"时,MapStruct生成的实现类UserMapperImpl会带上@Component注解。这样,你就可以在Spring的Service中直接@Autowired注入它了:
@Service public class UserService { @Autowired private UserMapper userMapper; // 直接注入 public UserVO getUserVO(Long id) { UserEntity entity = userRepository.findById(id).orElseThrow(); return userMapper.toVO(entity); // 像使用普通Bean一样使用 } }在Eclipse中的配置联动:为了让Spring能扫描到生成的Mapper实现类,你需要确保target/generated-sources/annotations目录在项目的编译类路径(classpath)中。我们之前在“触发代码生成”部分已经通过Java Build Path添加了。此外,你的Spring组件扫描路径(@ComponentScan)需要能覆盖Mapper接口所在的包。通常项目的主应用类在根包下,默认会扫描所有子包,所以一般没问题。
实操心得:使用
componentModel = "spring"后,最大的好处是便于单元测试。你可以在测试类中直接@Autowired注入Mapper,而不需要像Mappers.getMapper(...)那样去获取实例。同时,它也使得Mapper可以方便地注入其他Spring Bean(通过@Context注解,但需谨慎使用),实现更复杂的映射逻辑。不过要注意,一旦使用了Spring组件模型,这个映射器接口就不应该再定义INSTANCE静态常量了,因为实例将由Spring容器管理。
5. Eclipse中MapStruct的调试与问题排查
即使配置正确,开发过程中也难免遇到问题。掌握在Eclipse中调试MapStruct的技巧至关重要。
5.1 常见编译错误与解决方案
问题1:No property named “xxx” exists in source parameter(s).这是最常见的错误,意思是MapStruct在源对象中找不到你@Mapping(source=”xxx”)指定的属性。
- 检查拼写:仔细核对源对象中的属性名,注意大小写。
- 检查Getter方法:MapStruct默认通过getter方法(
getXxx()或isXxx())来访问属性。确保源对象有对应的public getter方法。如果你用了Lombok的@Data或@Getter,请确认注解已生效,且Eclipse的Lombok插件已安装。 - 检查导入:确保源对象和目标对象的类被正确定义和导入。
问题2:Unknown property “yyy” in result type Zzz.与问题1类似,但这次是目标对象的属性找不到。
- 检查目标对象:确认目标类存在
yyy属性及其setter方法(setYyy(...))。 - 检查
@Mapping(target=”yyy”):确认target的值与目标对象的属性名一致。
问题3:生成的实现类找不到或内容为空
- 检查注解处理配置:反复确认本章第二节“配置Eclipse的注解处理器”的步骤,尤其是
Factory Path里是否添加了mapstruct-processor的JAR。 - 检查输出目录:去
target/generated-sources/annotations下查看是否有文件生成。如果没有,尝试在项目上右键 ->Run As->Maven generate-sources。或者直接在命令行运行mvn compile。 - 检查依赖冲突:极少数情况下,其他注解处理器(如旧版本的Lombok)可能与MapStruct冲突。尝试更新所有依赖到最新稳定版,并确保
annotationProcessorPaths中Lombok在MapStruct之前。
问题4:与Lombok结合使用时,MapStruct报错找不到getter
- 顺序问题:确保在
pom.xml的annotationProcessorPaths中,lombok的路径在mapstruct-processor之前。 - Eclipse特定问题:在Eclipse中,即使Maven配置正确,有时也需要在
.project文件或特定设置中确保处理顺序。最彻底的解决方法是使用一个专用的Maven插件来管理注解处理器顺序,例如lombok-mapstruct-binding。在pom.xml中添加:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> <!-- 使用与Lombok兼容的版本 --> </dependency>然后将annotationProcessorPaths中的lombok路径替换为这个binding依赖。这个插件专门解决Lombok和MapStruct的协作问题。
5.2 如何调试生成的代码
MapStruct生成的代码是标准的Java类,位于target/generated-sources/annotations目录下。在Eclipse中调试它们非常直接:
- 设置断点:直接在生成的
XXXMapperImpl.java文件中的方法体内点击左侧边栏设置断点,就像调试你自己写的代码一样。 - 以Debug模式运行:运行你的单元测试或启动Spring Boot应用(Debug模式)。
- 触发映射调用:当程序执行到调用映射器方法的地方(例如
userMapper.toVO(entity)),Eclipse的调试器就会跳转到生成的实现类中对应的断点处。 - 查看变量:此时,你可以查看所有局部变量、源对象参数的值,单步执行每一行生成的映射代码,这对于理解复杂映射的行为、排查映射过程中数据不对的问题非常有帮助。
一个高级技巧:如果你怀疑MapStruct没有按照你的预期生成代码,或者想验证某个@Mapping注解是否生效,不要只看接口定义,直接去查看生成的Impl类。生成的代码是最权威的“文档”,它能清晰地展示最终生效的映射逻辑。
5.3 性能考量与最佳实践
MapStruct的性能接近手写代码,这是它最大的优势之一。但在Eclipse大型项目中,仍有一些实践可以优化开发体验和运行时效率:
- 集中管理Mapper:避免在每个需要转换的Service里散落着映射代码。建立一个清晰的
mapper包,所有映射器接口放在这里,便于管理和查找。 - 合理使用
componentModel:在Spring项目中使用componentModel = “spring”,让Spring管理Mapper的生命周期(默认单例)。避免在方法内部频繁使用Mappers.getMapper(...),这虽然也是单例,但不如Spring管理来得直观和统一。 - 谨慎使用
@Context参数:@Context允许你在映射方法中传入一个上下文参数,该参数会传递给所有嵌套映射。这可以用来传递诸如HttpServletRequest、Locale等信息,用于实现国际化或基于上下文的映射逻辑。但滥用会导致Mapper接口与特定运行环境耦合,降低可测试性。 - 为复杂映射编写单元测试:为你的Mapper接口编写简单的JUnit测试,验证基本字段映射、嵌套映射、类型转换是否正确。这能极大避免在运行时才发现映射错误。由于Mapper是无状态的,其测试非常简单。
- 关注生成代码的目录:确保
target/generated-sources/annotations不被提交到版本控制系统(如Git)。在.gitignore文件中添加target/。同时,在团队协作时,确保所有成员的Eclipse都正确配置了注解处理,避免因缺少生成代码而编译失败。 - 升级策略:关注MapStruct的版本更新。新版本通常会带来性能改进、Bug修复和新特性(如对Java新版本Record类的支持)。升级时,注意查看Release Notes中的不兼容变更。
在我多年的Eclipse开发经验中,MapStruct已经成为了对象映射事实上的标准选择。它完美地平衡了开发效率(声明式编程)、运行时性能(编译时生成)和代码可维护性(类型安全)。一旦在Eclipse中成功配置,它带来的开发体验提升是巨大的。从繁琐、易错的BeanUtils.copyProperties和手写setter中解放出来,将精力更多地投入到核心业务逻辑上,这正是现代Java开发者所需要的工具。希望这篇详尽的指南能帮助你在Eclipse世界里顺畅地驾驭MapStruct。