相关文章推荐
爱笑的回锅肉  ·  SQL Server SSL/TLS 加密 ...·  1 年前    · 
苦恼的小蝌蚪  ·  一日一技:除了 ...·  1 年前    · 

最近遇到一个工具类的问题比较容易出错,问题比较诡异,因为是通用的工具类,因此还是要搞清楚根本原因,避免误用。
由于领域模型之间的转换手写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 ( 1 L ) ; source . setSkuId ( 1 L ) ; 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);

这样就不会报错了。

有一些SourceType的列表->该列表元素的映射被添加到MappingContextImpl.sourceToDestination 具有该SourceType另一个属性,该属性等于列表的任何元素-> DestinationType的属性被添加到MappingContextImpl.intermediateDestination 具有的另一个属性SourceType不等于到列表中的任何其他元素- >在法MappingContextImpl.getParentDestination , intermediateDestination中搜索DestinationType的任 @Bean public ModelMapper modelMapper(){ ModelMapper modelMapper = new ModelMapper(); modelMapper.getConfiguration().setFullTypeMatchingRequired(true); modelMapper.g... ModelMapper是一个从对象到对象(object-to-object)的框架,能将Java Bean(Pojo)对象从一种表现形式转化为另一种表现形式。它采用“通过约定来配置”的方式,自动匹配不同的对象映射,同时具备满足某些特殊需求的高级功能。这与.NET的AutoMapper库很类似(但不是直接移植)。 ModelMapper能用更加紧凑的代码对Java对象进行映射,在更简单的情况下 <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>2.3.9</version> </dependency> 我有一个扩展Person对象的Student对象.public abstract class Person implements IIdentifiable {private String contactNumber;// other propertiespublic String getContactNumber() {return contactNumber;}public void setC... 警告:本文代码较多,请耐心阅读 在实际项目中,我们常常需要把两个相似的对象相互转换,其目的是在对外提供数据时需要将一部分敏感数据(例如:密码、加密token等)隐藏起来。最普通的方法是,新建一个对象,将需要的值逐个set进去。如果有多组需要这样转换的对象,那么就需要做很多只是get/set这样无意义的工作。 在这样的背景下,ModelMapper诞生了,它是一个简单、高效、智能的对象映射工具。它的... System.Configuration.ConfigurationErrorsException HResult=0x80131902 Message=Error creating context 'spring.root': 未能找到文件“H:\Code\Project\SpringDemo\Spring.NetDemo\Spring.NetDemo\bin\Debug\... 当我们使用 new ModelMapper类型转换遇到相似字段时编译器就会无法识别字段。 ProductOrderChangesItemDTO itemDTO = new ModelMapper() .map(iit, ProductOrderChangesItemDTO.class); 会抛出如下异常: 此时只需要给modelMapper 设置如下参数即可,此时就可以了 ModelMapper modelMapper = new ModelMapper(); //完全匹配字段 项目中对象与对象赋值转换使用的频率非常的高,比如数据库表实体对象(Entity)与业务类对象(Model)之间的赋值传递,或者模型对象(Model)与视图对象(ViewModel)之间的赋值传递。如果我们一个一个字段的赋值,将是非常繁琐并且毫无价值的重复工作,此时虽然我们可以自己通过反射提取个公共的方法来处理,但是更高效的方式是查看是否有第三方已经提供了比较成熟稳定的工具包,避免重复造轮子的工作。 在C#中我们一般使用AutoMapper作为对象转换工具(AutoMapper配置使用参考:https:// 通常,我们需要将数据库查询出来的数据封装到XXXDTO中再传给前端,如果是我们自己来进行封装的话,我们就需要用到繁杂的getter/setter来进行操作,不过有现成的轮子为啥不用呢?ModelMapper,就是这么一个工具,下面我们就来看下它的简单实用。 在进行演示之前,先添上工具的官网:点击这里进入ModelMapper官网 好,现在开始演示: 由于它是一个额外的轮子,第一步,我们需要导入该jar包 <dependency> <groupId>org.modelmapper& ModelMapper 中高级使用 java更多干货分布式实战(干货)spring cloud 实战(干货)mybatis 实战(干货)spring boot 实战(干货)React 入门实战(干货)构建中小型互联网企业架构(干货)python 学习持续更新ElasticSearch 笔记kafka storm 实战 (干货)概述ModelMapper是一个从对象到对象(object-to-obj... ModelMapper 高级使用   ModelMapper 是一个 Object To Object 的工具,类似于 MapStruct又不同于 MapStruct。主要原因是 ModelMapper 是利用反射的原理实现的 Object To Object。   ModelMapper 官方API : http://modelmapper.org/user-manual/property-ma...