第九章. MapStruct-Plus:自定义转换器与生命周期回调
第九章. MapStruct-Plus:自定义转换器与生命周期回调
Prorise第九章. MapStruct-Plus:自定义转换器与生命周期回调
摘要:在前面的章节中,我们依靠 @AutoMapping 和 expression 解决了很多字段映射问题。但在面对复杂的业务逻辑时(例如:根据身份证号计算年龄、调用 Redis 补充数据、依赖多字段的联合判断),在注解里写 Java 代码会变得极难维护。本章我们将引入 MapStruct 的 自定义装饰器 (Decorator) 模式,利用 uses 属性和 @AfterMapping 生命周期钩子,以最优雅的 Java 原生代码方式解决复杂的转换需求。
本章学习路径
- 痛点分析:理解为什么
expression不适合处理超过 1 行的复杂逻辑。 - 装饰器模式:定义一个独立的 Spring Bean 作为转换辅助类,支持依赖注入。
- 生命周期挂载:使用
@AfterMapping在自动转换完成后“补刀”,执行自定义逻辑。 - 实战演练:通过身份证号(BO 字段)自动计算出年龄、性别和星座(VO 字段)。
9.1. 突破注解的局限
在第六章和第七章中,我们使用了类似 expression = "java(JSONUtil.toJsonStr(...))" 的写法。这对于单行静态调用非常完美,但当遇到以下场景时,这种写法就变成了噩梦:
- 逻辑复杂:包含
if-else分支、循环或异常处理。 - 依赖注入:转换过程中需要查询数据库或 Redis(例如:把
userId转为userName)。 - 多字段联动:目标字段的值依赖源对象中的多个属性计算得出。
这时,我们需要将逻辑剥离到专门的 Java 类中,而不是塞在字符串里。
9.2. 引入自定义映射类 (Mapper Uses)
MapStruct Plus 完全兼容 MapStruct 原生的 uses 特性。我们可以定义一个普通的 Java 类(甚至可以是 Spring Bean),然后在 @AutoMapper 中引用它。
9.2.1. 定义需求
假设 UserBO 中有一个身份证号字段 idCard。在转为 UserVO 时,我们需要自动计算出:
age(年龄)genderText(性别中文)constellation(星座)
这些字段在 BO 中都不存在,且计算逻辑较复杂,适合使用 Hutool 的 IdcardUtil。
9.2.2. 定义辅助类 (CustomMapper)
这是一个普通的 Spring 组件。注意,为了方便 MapStruct 调用,方法的参数需要遵循特定规则。
文件路径:src/main/java/com/example/demo/infrastructure/converter/UserCustomMapper.java
1 | package com.example.demo.infrastructure.converter; |
关键点解析:
@AfterMapping:这是 MapStruct 的核心注解,表示该方法会在主转换逻辑执行之后被调用。@MappingTarget:标记哪个参数是“转换结果”。在这里,target是已经被 MSP 填充了一半的 VO 对象。
9.3. 配置 BO 关联辅助类
现在我们有了 UserCustomMapper,需要告诉 UserBO:“在转换时,请带上这个帮手”。
我们需要修改 UserBO,在 @AutoMapper 中添加 uses 属性。
文件路径:src/main/java/com/example/demo/domain/bo/UserBO.java
我们需要先在 UserBO 中添加 idCard 字段,并在 UserDetailVO 中添加对应的展示字段。
步骤 1:更新 UserDetailVO
1 | // src/main/java/com/example/demo/interfaces/vo/UserDetailVO.java |
步骤 2:更新 UserBO 并配置 uses
1 | package com.example.demo.domain.bo; |
9.4. 实战验证:计算逻辑生效
我们更新 Controller,模拟一个带有身份证号的 BO,验证 VO 中是否自动生成了年龄和性别。
文件路径:src/main/java/com/example/demo/controller/UserDecoratorController.java
1 | package com.example.demo.controller; |
运行结果预期:
访问 http://localhost:8080/users/calc-info。
控制台输出:
1 | >>> 自定义转换逻辑执行完毕,计算结果:[年龄:24, 性别:男] |
(注:年龄会根据当前年份自动变化)
HTTP 响应:
1 | { |
可以看到,虽然 UserBO 里只有一串冷冰冰的数字字符串,但 UserDetailVO 里却展现出了丰富的结构化信息。
9.5. 本章总结与自定义逻辑速查
本章我们突破了注解开发的最后一道防线,掌握了 MapStruct 强大的 Decorator(装饰器)模式。通过引入外部 Java 类和生命周期钩子,我们让映射过程具备了处理复杂业务(如身份证计算、数据库反查)的能力。
遇到以下 2 种复杂转换场景时,请直接 Copy 下方的标准代码模版:
9.5.1. 场景一:复杂计算与填充 (@AfterMapping)
需求:转换完成后,需要根据 BO 的 idCard 字段,自动计算并填充 VO 的 age 和 gender 字段。逻辑太长,不适合写在 expression 里。
方案:定义 @Component 类,使用 @AfterMapping 钩子。
1 | // 1. 定义辅助类 (必须注册为 Bean) |
9.5.2. 场景二:注入 Spring Service (查库映射)
需求:BO 中只有 deptId,VO 需要展示 deptName。需要调用 DeptService 查询数据库。
方案:在辅助类中注入 Service。
1 |
|







