前言:
MapStruct 是一个强大的 Java Bean 映射工具,默认情况下能自动处理类型匹配的字段。
但在实际开发中,经常需要自定义字段转换逻辑,比如日期格式转换、枚举映射、复杂对象转换等。以下是几种常见的自定义转换方法
一、自定义转换方法
- 基本类型转换
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel = "spring")
public interface UserMapper{
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
// 自定义转换方法
default String birthdayToString(Date date) {
if (date == null) {
return null;
}
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
return format.format(date);
}
// 使用 @Mapping 指定自定义转换方法
@Mapping(target = "birthDateStr", source = "birthday", qualifiedByName = "birthdayToString")
UserDto toDto(User user);
}
- 枚举映射
@Mapper(componentModel = "spring")
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
// 枚举映射方法
default OrderStatusEnum orderStatusToDto(OrderStatusEnum orderStatus) {
if (orderStatus== null) {
return null;
}
switch (orderStatus) {
case PAID:
return OrderStatusEnum.PAID;
case CANCELLED:
return OrderStatusEnum.CANCELLED;
default:
return OrderStatusEnum.PENDING;
}
}
@Mapping(target = "status", source = "orderStatus")
OrderDto toDto(Order order);
}
- @Mappings 注解自定义字段映射举例
@Mapper(componentModel = "spring")
public interface ProcessTaskMapper {
@Mappings({
@Mapping(source = "title", target = "titleName"), //不同字段名映射
@Mapping(target = "delFlag", ignore = true) //指定字段忽略转换
@Mapping( target = "type", defaultValue= "2") // 如果源属性为 null,则要设置的默认值。
@Mapping(target = "flowid", expression = "java(taskPushDTO.getProcessInstId().concat(taskPushDTO.getNodeId()))"), //java代码转换,字符串拼接
@Mapping(target = "platType", expression = "java(form.getLoginPlat() != null ? java.lang.Integer.valueOf(form.getLoginPlat()) : null)") //java 代码三元表达式
@Mapping(target = "createdTime",expression = "java(java.util.Date.from(taskPush.getGmtCreate().atZone(java.time.ZoneId.systemDefault()).toInstant()))"), //java.time.LocalDateTime 转换成 java.util.Date
@Mapping(source = "remark", target = "remarkJson", qualifiedByName = "converRemarkToJson"),
})// @qualifiedByName 指定使用自定义的转换方法转换
ProcessTaskPushDTO formToDto(ProcessTaskPushFrom taskPushFrom);
// 集合转JsonString
@Named("converRemarkToJson")
static String converRemarkJson(List<RemarkDTO> remarkDTOList) {
return CollectionUtil.isNotEmpty(remarkDTOList) ? JSON.toJSONString(remarkDTOList) : "";
}
}
二、使用 @Mapper 注解的 uses 属性
- 提取通用转换逻辑到单独类
// 通用转换器
public class DateConverter {
public String dateToString(Date date) {
if (date == null) {
return null;
}
return new SimpleDateFormat("yyyy-MM-dd").format(date);
}
public Date stringToDate(String dateStr) {
if (dateStr == null) {
return null;
}
try {
return new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}
}
// 在 Mapper 中引用转换器
@Mapper(uses = DateConverter.class)
public interface ProductMapper {
ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);
@Mapping(target = "createDateStr", source = "createDate")
ProductDto toDto(Product product);
}
三、复杂对象转换
- 嵌套对象转换
@Mapper
public interface AddressMapper {
AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
AddressDto toDto(Address address);
}
@Mapper(uses = AddressMapper.class)
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);
@Mapping(target = "addressDto", source = "address")
CustomerDto toDto(Customer customer);
}
- 集合转换
@Mapper
public interface ItemMapper {
ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class);
ItemDto toDto(Item item);
// MapStruct 会自动使用 toDto 方法进行集合转换
List<ItemDto> toDtoList(List<Item> items);
}
四、使用 @Context 传递上下文
- 带上下文的转换
// 上下文对象
public class LocaleContext {
private final Locale locale;
public LocaleContext(Locale locale) {
this.locale = locale;
}
public Locale getLocale() {
return locale;
}
}
@Mapper
public interface PriceMapper {
PriceMapper INSTANCE = Mappers.getMapper(PriceMapper.class);
// 使用 @Context 传递上下文
@Mapping(target = "formattedPrice", source = "price")
PriceDto toDto(Price price, @Context LocaleContext context);
default String formatPrice(BigDecimal price, @Context LocaleContext context) {
if (price == null) {
return null;
}
NumberFormat format = NumberFormat.getCurrencyInstance(context.getLocale());
return format.format(price);
}
}
五、高级自定义:@BeforeMapping 和 @AfterMapping 注解
- 映射前处理
@BeforeMapping,在执行映射逻辑之前填充目标映射DTO
@Mapper
public abstract class CarsMapper {
@BeforeMapping
protected void enrichDTOWithFuelType(Car car, @MappingTarget CarDTO carDto) {
if (car instanceof ElectricCar) {
carDto.setFuelType(FuelType.ELECTRIC);
}
if (car instanceof BioDieselCar) {
carDto.setFuelType(FuelType.BIO_DIESEL);
}
}
public abstract CarDTO toCarDto(Car car);
}
- 映射后处理
@AfterMapping注释方法的情况下在执行之后填充
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
@Mapping(target = "totalPrice", ignore = true) // 忽略自动映射
OrderDto toDto(Order order);
// 映射完成后计算总价
@AfterMapping
default void calculateTotalPrice(Order order, @MappingTarget OrderDto dto) {
if (order.getItems() != null) {
BigDecimal total = order.getItems().stream()
.map(Item::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
dto.setTotalPrice(total);
}
}
}
六、配置举例:
依赖配置:
xml
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>
编译选项:
// 在 Maven 中配置处理器选项
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>