如何用MapStruct避免为嵌套类编写映射器并处理日期转换?
问题描述
我有如下源类与目标类:
源类
class UserHistorySource { private DetailsSource details; }
class DetailsSource { private List<BalanceSource> balance; private List<RewardsSource> rewards; private List<LogsSource> logs; // 其他字段 } class BalanceSource { private String userId; private OffsetDateTime datetime; private String amount; } class RewardsSource { private String userId; private OffsetDateTime datetime; private String amount; } class LogsSource { private String userId; private OffsetDateTime datetime; private String amount; }
目标类
class UserHistoryTarget { private DetailsTarget details; } class DetailsTarget { private List<BalanceTarget> balance; private List<RewardsTarget> rewards; private List<LogsTarget> logs; // 其他字段 } class BalanceTarget { private String userId; private String datetime; private String amount; } class RewardsTarget { private String userId; private String datetime; private String amount; } class LogsTarget { private String userId; private String datetime; private String amount; }
目标类中的datetime字段为String类型,而源类中的datetime为OffsetDateTime类型。原本可以为Details、Balance、Rewards和Logs分别编写独立的映射器文件,并实现默认方法来完成OffsetDateTime到String的转换,但我希望避免为每个类编写映射器,请问是否有更优的解决方案?
最优解决方案
以下基于主流对象映射框架或通用工具,提供无需重复编写映射器的方案:
方案1:MapStruct全局类型转换器
MapStruct支持定义全局转换规则,一次编写即可覆盖所有涉及OffsetDateTime转String的映射场景:
- 定义全局转换器类:
import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; import org.mapstruct.Named; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public interface GlobalDateMapper { DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME; @Named("offsetDateTimeToString") default String offsetDateTimeToString(OffsetDateTime dateTime) { return dateTime != null ? dateTime.format(FORMATTER) : null; } }
- 编写顶层映射器并引入全局转换器:
import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; import org.mapstruct.ReportingPolicy; @Mapper( componentModel = MappingConstants.ComponentModel.SPRING, uses = GlobalDateMapper.class, // 关联全局转换器 unmappedTargetPolicy = ReportingPolicy.IGNORE ) public interface UserHistoryMapper { UserHistoryTarget map(UserHistorySource source); }
MapStruct会自动识别所有嵌套对象、集合中的OffsetDateTime字段,自动套用全局转换逻辑,无需为每个子类单独编写映射器。
方案2:ModelMapper全局类型转换器
ModelMapper可注册全局类型转换器,配置一次后自动递归处理所有嵌套对象的类型转换:
- 配置ModelMapper并注册转换器:
import org.modelmapper.ModelMapper; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; public class ModelMapperConfig { public static ModelMapper getModelMapper() { ModelMapper modelMapper = new ModelMapper(); // 注册OffsetDateTime转String的转换规则 modelMapper.addConverter(context -> { OffsetDateTime source = context.getSource(); return source != null ? source.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) : null; }, OffsetDateTime.class, String.class); return modelMapper; } }
- 使用时直接调用映射:
ModelMapper modelMapper = ModelMapperConfig.getModelMapper(); UserHistoryTarget target = modelMapper.map(userHistorySource, UserHistoryTarget.class);
框架会自动处理所有层级的对象复制与类型转换,无需编写任何子类映射规则。
方案3:通用工具类+BeanUtils(无框架依赖)
如果不想引入第三方框架,可结合Apache Commons BeanUtils和自定义转换工具实现:
- 编写日期转换工具类:
import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; public class DateConvertUtil { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME; public static String offsetDateTimeToString(OffsetDateTime dateTime) { return dateTime != null ? dateTime.format(FORMATTER) : null; } }
- 实现对象转换逻辑:
import org.apache.commons.beanutils.BeanUtils; import java.util.stream.Collectors; public class ObjectConverter { public static UserHistoryTarget convert(UserHistorySource source) throws Exception { UserHistoryTarget target = new UserHistoryTarget(); BeanUtils.copyProperties(target, source); DetailsTarget detailsTarget = new DetailsTarget(); BeanUtils.copyProperties(detailsTarget, source.getDetails()); // 批量转换Balance列表 detailsTarget.setBalance(source.getDetails().getBalance().stream() .map(balanceSource -> { BalanceTarget balanceTarget = new BalanceTarget(); try { BeanUtils.copyProperties(balanceTarget, balanceSource); balanceTarget.setDatetime(DateConvertUtil.offsetDateTimeToString(balanceSource.getDatetime())); } catch (Exception e) { e.printStackTrace(); } return balanceTarget; }).collect(Collectors.toList())); // 同理转换Rewards和Logs列表 detailsTarget.setRewards(source.getDetails().getRewards().stream() .map(rewardsSource -> { RewardsTarget rewardsTarget = new RewardsTarget(); try { BeanUtils.copyProperties(rewardsTarget, rewardsSource); rewardsTarget.setDatetime(DateConvertUtil.offsetDateTimeToString(rewardsSource.getDatetime())); } catch (Exception e) { e.printStackTrace(); } return rewardsTarget; }).collect(Collectors.toList())); detailsTarget.setLogs(source.getDetails().getLogs().stream() .map(logsSource -> { LogsTarget logsTarget = new LogsTarget(); try { BeanUtils.copyProperties(logsTarget, logsSource); logsTarget.setDatetime(DateConvertUtil.offsetDateTimeToString(logsSource.getDatetime())); } catch (Exception e) { e.printStackTrace(); } return logsTarget; }).collect(Collectors.toList())); target.setDetails(detailsTarget); return target; } }
该方案无需第三方框架,但需手动处理集合转换,适合字段较少的简单场景。
内容的提问来源于stack exchange,提问作者user641887
相关产品推荐
相关产品推荐

