第五章. MapStruct-Plus :数据传输对象 DTO 到业务对象 BO 的相互映射
第五章. MapStruct-Plus :数据传输对象 DTO 到业务对象 BO 的相互映射
Prorise第五章. MapStruct-Plus :数据传输对象 DTO 到业务对象 BO 的相互映射
摘要:本章我们将正式引入 MapStruct-Plus (MSP),通过“入站”数据流(DTO -> BO)的实战,彻底重构之前的开发模式。我们将建立符合阿里巴巴规范的分层架构,深入理解 MSP 如何通过 @AutoMapper 注解消除繁琐的接口定义,并掌握全局 Converter 的依赖注入机制。
本章学习路径
- 环境重塑:清理旧的 MapStruct 依赖,引入 MSP Starter,并配置关键的编译顺序(AST 冲突解决)。
- 架构落地:初始化
client(传输层) 与domain(领域层) 的包结构。 - 核心对比:通过 Tab 对比,直观感受“接口优先”与“注解优先”的差异。
- 入站实战:编写
UserCreateDTO,使用@AutoMapper建立通往UserBO的数据桥梁。 - 全局调用:掌握
io.github.linpeilie.Converter的统一调用方式。
5.1. 环境依赖清洗与重构
在开始新的架构之前,我们需要确保工程环境的纯净。MSP (MapStruct-Plus) 是基于 MapStruct 的增强封装,为了避免类加载冲突(Jar Hell),我们需要移除原生的 MapStruct 依赖,转而使用 MSP 的全家桶 Starter。
5.1.1. 依赖变更
文件路径:pom.xml
请打开项目根目录下的 pom.xml 文件,执行以下操作:
- 删除:移除原有的
org.mapstruct:mapstruct和mapstruct-processor依赖。 - 新增:引入
mapstruct-plus-spring-boot-starter。
1 | <dependencies> |
5.1.2. 编译插件配置(至关重要)
MapStruct 和 Lombok 都是基于 JSR-269 的注解处理器(Annotation Processor)。它们在编译期修改字节码(AST 修改)。
- Lombok:生成 getter/setter。
- MapStruct:读取 getter/setter 生成转换代码。
如果 MapStruct 先执行,它会发现对象里没有 getter/setter(因为 Lombok 还没干活),从而导致无法生成映射代码。因此,必须严格控制插件的执行顺序。
文件路径:pom.xml -> <build><plugins>
1 | <plugin> |
5.2. 初始化分层包结构
根据阿里巴巴 Java 开发手册的分层规范,我们不再把所有类都堆在 entity 包下。我们需要明确区分 数据传输对象 (DTO) 和 业务对象 (BO)。
5.2.1. 创建目录
请在 IDE 中按照以下结构创建包:
目录树结构:
1 | src/main/java/com/example/demo/ |
- client.dto:这是“入站”的最前线,接收前端传来的 JSON 参数。
- domain.bo:这是业务的内核,Service 层只处理 BO,不关心 DTO 的存在。
5.3. MSP 核心理念:零接口开发
在编写代码前,我们必须理解 MSP 究竟改变了什么。它将 MapStruct 的 Interface-First(接口定义优先) 模式转变为 Annotation-First(注解绑定优先) 模式。
以下通过对比展示两种模式在实现 DTO -> BO 时的差异:
繁琐的接口定义模式
在原生模式下,你需要手动创建一个接口文件,添加 @Mapper 注解,定义方法签名。随着业务增长,这个接口文件会变得极其庞大且难以维护。
1 | // 必须手动创建 Mapper 接口 |
极速的注解驱动模式
在 MSP 模式下,Mapper 接口文件消失了。你只需要在类头上加一个 @AutoMapper 注解,编译器会自动帮你生成背后的接口和实现类。
1 | // 直接在类上声明转化关系 |
5.4. 入站实战:DTO 到 BO 的转化
现在我们模拟一个用户注册场景。前端提交了用户名、手机号和密码,我们需要将这些数据转化为业务对象,以便在 Service 层进行处理。
5.4.1. 定义业务对象 (BO)
首先定义转化的 目标,即业务对象。BO 对象应该包含业务逻辑所需的所有属性,它是纯净的,不包含任何 @NotNull 等前端校验注解。
文件路径:src/main/java/com/example/demo/domain/bo/UserBO.java
1 | package com.example.demo.domain.bo; |
5.4.2. 定义传输对象 (DTO) 并绑定映射
接下来定义 源头,即传输对象。DTO 负责接收外部参数,并承载基础的格式校验。
关键操作:我们需要在 DTO 上添加 @AutoMapper 注解,告诉 MSP:“请在编译时生成代码,将本类转换为 UserBO”。
文件路径:src/main/java/com/example/demo/client/dto/UserCreateDTO.java
1 | package com.example.demo.client.dto; |
5.4.3. 编写 Controller 进行全链路验证
配置完成后,我们不需要写任何 Mapper 接口,直接在 Controller 中注入 MSP 的全局转换器 Converter。
文件路径:src/main/java/com/example/demo/controller/UserRegistrationController.java
1 | package com.example.demo.controller; |
5.4.4. 启动验证与代码审计
步骤 1:执行编译
在终端执行 mvn clean compile。此时 MSP 的注解处理器开始工作。
步骤 2:生成代码审计
请到项目的 target/generated-sources/annotations 目录下查看。你应该能找到一个名为 com.example.demo.client.dto.UserCreateDTOToUserBOMapper 的类。
1 | // 自动生成的代码片段 |
步骤 3:Postman 调用
发送 POST 请求到 http://localhost:8080/users/register。
Body:
1 | { |
响应结果:
1 | { |
结果分析:
username,phone转换成功:证明@AutoMapper生效。source为null:证明 MSP 默认只处理同名属性,异名属性(platformvssource)被忽略了。我们在下一章处理这个问题。
5.5. 全局策略配置
在刚才的实战中,platform 字段因为没有匹配到目标字段而被静默忽略了。在生产环境中,这种“静默”是非常危险的,可能导致数据丢失而不自知。
我们需要配置 MSP,使其在发现未映射字段时发出警告。
5.5.1. 创建配置类
MSP 提供了 @MapperConfig 注解来控制全局行为,我们首先需要开启 pom.xml 下的 maven 编译预警
1 | <configuration> |
然后再配置类中新增配置
文件路径:src/main/java/com/example/demo/config/MapStructPlusConfig.java
1 | package com.example.demo.config; |
配置完成后,再次执行 mvn compile,如果 UserBO 中有字段未被赋值,控制台将会打印 Warning 日志,提醒开发者检查映射规则。
5.6. 本章小结
本章我们完成了从“传统 Mapper 接口”到“MSP 注解驱动”的架构转型,并打通了 入站 (DTO -> BO) 的数据链路。
核心要点:
- 架构分层:DTO 用于传输,BO 用于业务,两者通过 MSP 解耦。
- 零接口:在 Source 类上使用
@AutoMapper(target = Target.class)即可自动生成转换器。 - 统一调用:注入
Converter接口,使用.convert(source, targetClass)方法,无需关心底层实现。
场景化代码速查:
场景:前端传入注册表单,需要转为业务对象。
方案:
1 | // 1. DTO 定义 (Source) |
在下一章中,我们将深入业务核心层。UserBO 需要被持久化到数据库(转换 UserPO),这中间将面临 枚举转换 (Enum vs Int) 和 复杂 JSON 字段 的挑战,我们将展示 MSP 如何与 MyBatis-Plus 完美配合解决这些难题。








