AutoMapper的源码分析
最近有一个小项目需要提供接口给第三方使用,接口会得到一个大的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 Type
和destination 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 type
和destination 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的源码分析相关推荐
- 【转】ABP源码分析一:整体项目结构及目录
ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...
- 【转】ABP源码分析三十一:ABP.AutoMapper
这个模块封装了Automapper,使其更易于使用. 下图描述了改模块涉及的所有类之间的关系. AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttri ...
- 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析
目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...
- SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)
[SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) SpringBoot-web开发(二): 页面和图标定制(源码分析) SpringBo ...
- SpringBoot-web开发(二): 页面和图标定制(源码分析)
[SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...
- SpringBoot-web开发(一): 静态资源的导入(源码分析)
目录 方式一:通过WebJars 1. 什么是webjars? 2. webjars的使用 3. webjars结构 4. 解析源码 5. 测试访问 方式二:放入静态资源目录 1. 源码分析 2. 测 ...
- Yolov3Yolov4网络结构与源码分析
Yolov3&Yolov4网络结构与源码分析 从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗. 文章目录 论文汇总 ...
- ViewGroup的Touch事件分发(源码分析)
Android中Touch事件的分发又分为View和ViewGroup的事件分发,View的touch事件分发相对比较简单,可参考 View的Touch事件分发(一.初步了解) View的Touch事 ...
- View的Touch事件分发(二.源码分析)
Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...
最新文章
- java包装器类_Java中的基本类型和包装类
- 关掉windows自动更新
- 【struts2+spring+hibernate】ssh框架整合开发
- 169. Majority Element
- mysql事务隔离级别与锁_mysql事务隔离级别与锁
- 【身份认证及权限控制一】单点登录
- HDU-1269 Tarjan求强连通分量,模板题
- 最速下降法(梯度下降法)
- AJAX 异步加载技术
- 最常见的Web服务器市场份额
- Kerberos 协议和 KDC 实现 Apache Kerby
- linux操作实例,linux下的一些文档操作实例 | Soo Smart!
- unity透明物体显示问题
- 基于Java生鲜蔬菜食品商城系统详细设计和实现
- android手机at指令集,手机AT指令集
- linux ssh freeradius,如何将SSH身份验证配置到FreeRADIUS服务器
- 什么时候使用PHP设计模式和为什么要使用?
- 竞价域名是干什么的?为什么要进行域名竞价?
- 对接快递100快递管家API之如何实现自动打单
- 视频时序动作识别(video action recognition)介绍
热门文章
- [Linux]Windows使用ssh连接Linux虚拟机(mininet)
- axure9中继器使用
- 6.牛批了 Android 2021中高级面试题 一线大厂和二线大厂面试真题精选 (小米 附答案)第三套 28k+
- Processing编程自画像
- yjk的波库在哪里_盈建科学习资料 YJKEP弹塑性软件说明手册.pdf
- Java线程锁(一)
- 移动端app开发,框架的选择。
- 小米6更新系统显示无网络连接到服务器,小米6刷上统信 UOS 系统,操作流畅但安装需谨慎...
- Android渲染画面,Android系统图像渲染简介
- 全志V3S荔枝派zero10分钟制作TF启动卡,主线Linux,主线u-boot(超过10分钟博主直播吃奥利奥!!!)