使用 MapStruct 的 @AfterMapping 注解不生效,与Lombok的@Builder冲突
- 简介
- 需求场景
- 问题现象
- 问题原因
- 解决方案
- 代码实现
- // 方案一代码实现:
- // 方案二代码实现:
在 Quarkus 框架项目中使用了 MapStruct 的 @AfterMapping 注解不生效,究其原因是在对应实体类中使用了 Lombok 的 @Builder 注解,两者在使用上冲突了。
简介
MapStruct:MapStruct - Java bean mappings, the easy way!
Lombok:Very spicy additions to the Java programming language.
基于projectlombok-version:1.18.30、org.mapstruct.version:1.5.5.Final
需求场景
我的一个POJO转换为一个DTO对象,其中对象一个属性为List<Object>
类型,我需要在转换后对该属性值得集合进行排序。故我想到了使用MapStruct的@AfterMapping
注解, 该注解标注的默认方法能在对象转换时(标记要在生成的映射方法的末尾、映射方法的最后一条语句之前 return 调用的方法。)执行相关的自定义方法逻辑。这里对于我的需求就是返回转换目标之前对其List<Object>
属性数据进行排序。
问题现象
我在 Mapper.java 类中编写的被@AfterMapping
注解标注的代码/逻辑并没有添加到 @ generated Implementation class 类中。
Mapper.java
@Mapper(componentModel = "cdi")
public interface StatusConverter {@Mappings({@Mapping(target = "orderId", source = "status.orderId"),@Mapping(target = "statusList", source = "statusLogList")})StatusDetail mapTo(OrderStatus status, List<StatusDetail.StatusLog> statusLogList);@AfterMappingdefault void sortStatusList(@MappingTarget StatusDetail statusDetail) {if (Objects.nonNull(statusDetail.getStatusList())) {statusDetail.getStatusList().sort(Comparator.comparing(StatusDetail.StatusLog::getUpdatedAt));}}
}
Generated Implementation class
@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2024-12-03T14:06:26+0800",comments = "version: 1.5.5.Final, compiler: javac, environment: Java 17.0.11 (Eclipse Adoptium)"
)
@ApplicationScoped
public class StatusConverterImpl implements StatusConverter {@Overridepublic StatusDetail mapTo(OrderStatus status, List<StatusDetail.StatusLog> statusLogList) {if ( status== null && statusLogList== null ) {return null;}StatusDetail.StatusDetailBuilder statusDetail = StatusDetail.builder();if ( status != null ) {statusDetail.orderId( status.getOrderId() );}List<StatusDetail.StatusLog> list = statusLogList;if ( list != null ) {statusDetail.statusList( new ArrayList<StatusDetail.StatusLog>( list ) );}// FIXME: 注意这里并没有调用 @AfterMapping 标注的方法return statusDetail.build();}
}
问题原因
我在StatusDetail
实体类中使用了 Lombok 的 @Builder
注解, 由于使用了@Builder
注解, MapStruct在编译时, 会用EntityBuilder.build()
完成目标对象的实例化,而非使用new
对象后setter
的方式实例化对象。在生成的映射方法的末尾、映射方法的最后一条语句 return 之前只能提供StatusDetail.StatusDetailBuilder对象, 而非StatusDetail对象,而@AfterMapping标注的方法参数是需要StatusDetail对象,所以未调用
。 实体类代码如下所示:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StatusDetail {private String orderId;private List<StatusLog> statusList;@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic static class StatusLog {private String id;private String orderId;private String sourceStatus;private String targetStatus;private Instant createdAt;private Instant updatedAt;}
}
解决方案
@Mapper
上禁用对于builder的支持,@builder#disableBuilder
@AfterMapping
标注方法上@MappingTarget
标注参数由Bean
改为BeanBuilder
对象
代码实现
// 方案一代码实现:
@Mapper(componentModel = "cdi", builder = @Builder(disableBuilder = true))
public interface StatusConverter {@Mappings({@Mapping(target = "orderId", source = "status.orderId"),@Mapping(target = "statusList", source = "statusLogList")})StatusDetail mapTo(OrderStatus status, List<StatusDetail.StatusLog> statusLogList);@AfterMappingdefault void sortStatusList(@MappingTarget StatusDetail statusDetail) {if (Objects.nonNull(statusDetail.getStatusList())) {statusDetail.getStatusList().sort(Comparator.comparing(StatusDetail.StatusLog::getUpdatedAt));}}
}
// @generated impl
@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2024-12-03T15:23:29+0800",comments = "version: 1.5.5.Final, compiler: javac, environment: Java 17.0.11 (Eclipse Adoptium)"
)
@ApplicationScoped
public class StatusConverterImpl implements StatusConverter {@Overridepublic StatusDetail mapTo(OrderStatus status, List<StatusDetail.StatusLog> statusLogList) {if ( status== null && statusLogList== null ) {return null;}StatusDetail statusDetail = new StatusDetail();if ( status != null ) {statusDetail.setOrderId( status.getOrderId() );}List<StatusDetail.StatusLog> list = statusLogList;if ( list != null ) {statusDetail.setStatusList( new ArrayList<StatusDetail.StatusLog>( list ) );}sortStatusList( statusDetail );return statusDetail;}
}
// 方案二代码实现:
@Mapper(componentModel = "cdi")
public interface StatusConverter {@Mappings({@Mapping(target = "orderId", source = "status.orderId"),@Mapping(target = "statusList", source = "statusLogList")})StatusDetail mapTo(OrderStatus status, List<StatusDetail.StatusLog> statusLogList);@AfterMappingdefault void sortStatusList(@MappingTarget DepositStatusDetail.OrderStatusBuilder statusDetailBuilder, List<OrderStatus.StatusLog> statusList) {if (CollectionUtil.isNotEmpty(statusList)) {statusDetailBuilder.statusList(statusList.stream().sorted(Comparator.comparing(StatusDetail.StatusLog::getUpdatedAt)).toList());}}
}
// @generated impl
@Generated(value = "org.mapstruct.ap.MappingProcessor",date = "2024-12-03T15:15:58+0800",comments = "version: 1.5.5.Final, compiler: javac, environment: Java 17.0.11 (Eclipse Adoptium)"
)
@ApplicationScoped
public class StatusConverterImpl implements StatusConverter {@Overridepublic StatusDetail mapTo(OrderStatus status, List<StatusDetail.StatusLog> statusLogList) {if ( status== null && statusLogList== null ) {return null;}StatusDetail.StatusDetailBuilder statusDetailBuilder = StatusDetail.builder();if ( status != null ) {statusDetail.orderId( status.getOrderId() );}List<StatusDetail.StatusLog> list = statusLogList;if ( list != null ) {statusDetail.statusList( new ArrayList<StatusDetail.StatusLog>( list ) );}sortStatusList( statusDetailBuilder, statusList );return statusDetail.build();}
}
参考:
https://github.com/mapstruct/mapstruct/issues/1556
https://blog.csdn.net/nxhljt/article/details/123893492