modelmapper属性匹配问题分析

最近遇到一个工具类的问题比较容易出错,问题比较诡异,因为是通用的工具类,因此还是要搞清楚根本原因,避免误用。
由于领域模型之间的转换手写getter setter代码会很冗余并且容易出错,然而复制对象属性值是高频操作,因此各种库有开源实现用以复制对象属性,常见的有spring和apache的实现,个人更推荐spring的版本,根据经验发现spring的BUG更少。这次在项目中用一个现成的封装工具类来复制属性,发现一个比较诡异的报错,查看其实现用的org.modelmapper实现复制属性,这次就来看一下它的实现到底是怎样的,首先报错代码demo如下:

    /**
     * 复制的目标model
     */
    static class Target {
        private Long id;
       // getter setter略
    }

    /**
     * 复制的来源model
     */
    static class Source {
        private Long skuId;
        private Long itemId;
		// getter setter略
    }
    public static void main(String[] args) {
        Source source = new Source();
        source.setItemId(1L);
        source.setSkuId(1L);

        Target target = new Target();
        ModelMapper modelMapper = new ModelMapper();

        // 复制model属性
        modelMapper.map(source, target);
        System.out.println(target);
    }

报错堆栈:

Exception in thread "main" org.modelmapper.ConfigurationException: ModelMapper configuration errors:

1) The destination property Target.setId() matches multiple source property hierarchies:

	Source.getSkuId()
	Source.getItemId()

1 error
	at org.modelmapper.internal.Errors.throwConfigurationExceptionIfErrorsExist(Errors.java:241)
	at org.modelmapper.internal.ImplicitMappingBuilder.matchDestination(ImplicitMappingBuilder.java:150)
	at org.modelmapper.internal.ImplicitMappingBuilder.build(ImplicitMappingBuilder.java:81)
	at org.modelmapper.internal.TypeMapStore.getOrCreate(TypeMapStore.java:108)
	at org.modelmapper.internal.TypeMapStore.getOrCreate(TypeMapStore.java:81)
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:99)
	at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:60)
	at org.modelmapper.ModelMapper.mapInternal(ModelMapper.java:529)
	at org.modelmapper.ModelMapper.map(ModelMapper.java:413)

我的预期不应该匹配到值,而这报错含义就是id匹配到source中Source.getSkuId(),Source.getItemId()两个属性值,这非常的奇怪。

其调用链路大概如下:
在这里插入图片描述
也就是说到底层其会调用一个Matcher去遍历属性匹配,而就是在这个匹配的过程中发生了问题。
大概分析下调用链路:

  1. 调用开始,map传递来源对象和目标对象。
    在这里插入图片描述
  2. Types类反射获取到对象类型并调用modelmapper自己实现的一个Engine默认实现开始map在这里插入图片描述
  3. 把需要进行mapper的上下文进行解析并封装
    在这里插入图片描述
  4. 可能是解析对象匹配属性对的过程比较消耗资源,所以这里主要看当前mapper对象有没缓存的匹配转换工具等,如果有就可以直接用,否则创建。
    在这里插入图片描述
  5. 创建匹配关系在这里插入图片描述
  6. 这里终于开始目标字段的遍历匹配了在这里插入图片描述
  7. 这里对目标对象id进行匹配在这里插入图片描述
  8. 这里匹配的结果是itemId与id的匹配是true,问题的根本也就在这里在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    查看它的匹配策略有三种实现,这里使用的方式是标准匹配在这里插入图片描述
  9. 这里属性对的匹配是根据驼峰拆分,统计匹配数量在这里插入图片描述在这里插入图片描述
    itemId与id的匹配数量是1,skuId与id的匹配也是1,所以发现两个来源属性导致导致异常。
    解决方案:
    看源码分析其实可以看到关键的问题在于属性匹配的策略不符合我们的预期,竟然把标准匹配策略设计成这样的逻辑,至少我在大多数情况下不会依赖驼峰拆分匹配数量去复制属性,这样在属性多的情况下很难控制,更多情况应该是严格的一个匹配策略,它原生的实现的确也有一个严格匹配策略 StrictMatchingStrategy.
    修改方法:
		Source source = new Source();
        source.setItemId(1L);
        source.setSkuId(1L);

        Target target = new Target();
        ModelMapper modelMapper = new ModelMapper();

        // 修改匹配策略为严格
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        modelMapper.map(source, target);
        System.out.println(target);

这样就不会报错了。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值