最近有一个小项目需要提供接口给第三方使用,接口会得到一个大的XML的字符串大约有8个对象100多个字段,在映射到Entity只能通过反射来赋值避免重复的赋值,但是明显感觉到性能下降严重,因为以前接触过AutoMapper所以写了一篇博客记录其中的实现原理。

在github 上可以下载AutoMapper 源码,直接打开sln 文件 就可以看到项目结构。

项目结构非常清晰,一个AutoMapper的实现类,两个测试库一个单元测试一个集成测试还有一个性能测试。下面就是AutoMapper的测试代码。

var config = new MapperConfiguration(cfg => cfg.CreateMap<TestSource, TestDestination>()
);
var mapper1 = config.CreateMapper();
var fooDto = mapper1.Map<TestDestination>(new TestSource{MyProperty = 1
});

项目不大,调试的时候你就可以明白AutoMapper的实现原理,下面就是一个大致的分析过程。

代码创建了一个MapperConfiguration对象,里面传递了一个Action到构造方法,而这个Action里创建了Map 的信息,这是最简单的demo所以使用默认的字段Map方式。

public MapperConfiguration(Action<IMapperConfigurationExpression> configure): this(Build(configure))
{}private static MapperConfigurationExpression Build(Action<IMapperConfigurationExpression> configure)
{var expr = new MapperConfigurationExpression();configure(expr);return expr;
}

上面就是构造方法处理的逻辑,实际上就是build方法创造一个MapperConfigurationExpression对象,然后把这个对象传给Action生成Mapper,这个Mapper的意思就是source Typedestination Type的字段映射对象。我们继续查看这个第8行的代码在new这个对象的时候做了什么初始化操作。这个MapperConfigurationExpression继承了抽象类Profile,而在这个父类的构造方法里初始化了IMemberConfiguration类,这个是为了制定map规则对象,默认是DefaultMember这个对象。下面是这个创建完MapperConfigurationExpression后调用构造方法。

public MapperConfiguration(MapperConfigurationExpression configurationExpression)
{_mappers = configurationExpression.Mappers.ToArray();_resolvedMaps = new LockingConcurrentDictionary<TypePair, TypeMap>(GetTypeMap);_executionPlans = new LockingConcurrentDictionary<MapRequest, Delegate>(CompileExecutionPlan);_validator = new ConfigurationValidator(this, configurationExpression);ExpressionBuilder = new ExpressionBuilder(this);ServiceCtor = configurationExpression.ServiceCtor;EnableNullPropagationForQueryMapping = configurationExpression.EnableNullPropagationForQueryMapping ?? false;MaxExecutionPlanDepth = configurationExpression.Advanced.MaxExecutionPlanDepth + 1;ResultConverters = configurationExpression.Advanced.QueryableResultConverters.ToArray();Binders = configurationExpression.Advanced.QueryableBinders.ToArray();RecursiveQueriesMaxDepth = configurationExpression.Advanced.RecursiveQueriesMaxDepth;Configuration = new ProfileMap(configurationExpression);Profiles = new[] { Configuration }.Concat(configurationExpression.Profiles.Select(p => new ProfileMap(p, configurationExpression))).ToArray();configurationExpression.Features.Configure(this);foreach (var beforeSealAction in configurationExpression.Advanced.BeforeSealActions)beforeSealAction?.Invoke(this);Seal();
}

在这个构造方法里会将之前创建的MapperConfigurationExpression转化成当前对象的各个字段,其中LockingConcurrentDictionary是自己实现的线程安全的字典对象,构造方法传递取值的规则,重要的是Profiles字段,这个存储之前的action 传递的MapperConfigurationExpression对象,这个对象也就是source typedestination type 对象的相关信息。最后重要的是seal 方法,根据相关信息生产lambda代码。

public void Seal(IConfigurationProvider configurationProvider)
{if(_sealed){return;}_sealed = true;_inheritedTypeMaps.ForAll(tm => _includedMembersTypeMaps.UnionWith(tm._includedMembersTypeMaps));foreach (var includedMemberTypeMap in _includedMembersTypeMaps){includedMemberTypeMap.TypeMap.Seal(configurationProvider);ApplyIncludedMemberTypeMap(includedMemberTypeMap);}_inheritedTypeMaps.ForAll(tm => ApplyInheritedTypeMap(tm));_orderedPropertyMaps = PropertyMaps.OrderBy(map => map.MappingOrder).ToArray();_propertyMaps.Clear();MapExpression = CreateMapperLambda(configurationProvider, null);Features.Seal(configurationProvider);
}

上面就是核心代码,根据之前注册的相关信息生成一个lambda代码。CreateDestinationFunc创建一个lambda表达式,内容是new 一个destination对象,在CreateAssignmentFunc继续扩展字段赋值的lambda内容,其中字段map规则就是之前新建MapperConfiguration

对象的时候创建的,如果没有注入就是默认的匹配规则,CreateMapperFunc会产生一些规则,比如默认值赋值等等。这些生成的lambda对象会存在Typemap 对象的MapExpression中。

public LambdaExpression CreateMapperLambda(HashSet<TypeMap> typeMapsPath)
{var customExpression = TypeConverterMapper() ?? _typeMap.CustomMapFunction ?? _typeMap.CustomMapExpression;if(customExpression != null){return Lambda(customExpression.ReplaceParameters(Source, _initialDestination, Context), Source, _initialDestination, Context);}CheckForCycles(typeMapsPath);if(typeMapsPath != null){return null;}var destinationFunc = CreateDestinationFunc();var assignmentFunc = CreateAssignmentFunc(destinationFunc);var mapperFunc = CreateMapperFunc(assignmentFunc);var checkContext = CheckContext(_typeMap, Context);var lambaBody = checkContext != null ? new[] {checkContext, mapperFunc} : new[] {mapperFunc};return Lambda(Block(new[] {_destination}, lambaBody), Source, _initialDestination, Context);
}

后来的调用map的时候就会这些lambda方法,在之前说过,生成的lambda表达式会在内存中动态生成IL代码,这些花费的时间只有一次就是seal调用的时候,后面的时候去运行代码速度会快得多,因为这些动态生成的il代码在运行的时候速度和手写代码运行的一样的,所以代码运行的时候是非常快的,远远高于反射的速度,所以这就是AutoMapper运行速度快的原因。这个项目挺小的并且代码工整可读性挺强的,希望大家在阅读源代码能够学的更多。最后谢谢大家。

AutoMapper的源码分析相关推荐

  1. 【转】ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. 【转】ABP源码分析三十一:ABP.AutoMapper

    这个模块封装了Automapper,使其更易于使用. 下图描述了改模块涉及的所有类之间的关系. AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttri ...

  3. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  4. SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) SpringBoot-web开发(二): 页面和图标定制(源码分析) SpringBo ...

  5. SpringBoot-web开发(二): 页面和图标定制(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...

  6. SpringBoot-web开发(一): 静态资源的导入(源码分析)

    目录 方式一:通过WebJars 1. 什么是webjars? 2. webjars的使用 3. webjars结构 4. 解析源码 5. 测试访问 方式二:放入静态资源目录 1. 源码分析 2. 测 ...

  7. Yolov3Yolov4网络结构与源码分析

    Yolov3&Yolov4网络结构与源码分析 从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗. 文章目录 论文汇总 ...

  8. ViewGroup的Touch事件分发(源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,View的touch事件分发相对比较简单,可参考 View的Touch事件分发(一.初步了解) View的Touch事 ...

  9. View的Touch事件分发(二.源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...

最新文章

  1. java包装器类_Java中的基本类型和包装类
  2. 关掉windows自动更新
  3. 【struts2+spring+hibernate】ssh框架整合开发
  4. 169. Majority Element
  5. mysql事务隔离级别与锁_mysql事务隔离级别与锁
  6. 【身份认证及权限控制一】单点登录
  7. HDU-1269 Tarjan求强连通分量,模板题
  8. 最速下降法(梯度下降法)
  9. AJAX 异步加载技术
  10. 最常见的Web服务器市场份额
  11. Kerberos 协议和 KDC 实现 Apache Kerby
  12. linux操作实例,linux下的一些文档操作实例 | Soo Smart!
  13. unity透明物体显示问题
  14. 基于Java生鲜蔬菜食品商城系统详细设计和实现
  15. android手机at指令集,手机AT指令集
  16. linux ssh freeradius,如何将SSH身份验证配置到FreeRADIUS服务器
  17. 什么时候使用PHP设计模式和为什么要使用?
  18. 竞价域名是干什么的?为什么要进行域名竞价?
  19. 对接快递100快递管家API之如何实现自动打单
  20. 视频时序动作识别(video action recognition)介绍

热门文章

  1. [Linux]Windows使用ssh连接Linux虚拟机(mininet)
  2. axure9中继器使用
  3. 6.牛批了 Android 2021中高级面试题 一线大厂和二线大厂面试真题精选 (小米 附答案)第三套 28k+
  4. Processing编程自画像
  5. yjk的波库在哪里_盈建科学习资料 YJKEP弹塑性软件说明手册.pdf
  6. Java线程锁(一)
  7. 移动端app开发,框架的选择。
  8. 小米6更新系统显示无网络连接到服务器,小米6刷上统信 UOS 系统,操作流畅但安装需谨慎...
  9. Android渲染画面,Android系统图像渲染简介
  10. 全志V3S荔枝派zero10分钟制作TF启动卡,主线Linux,主线u-boot(超过10分钟博主直播吃奥利奥!!!)