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方式。
1 public MapperConfiguration(Action<IMapperConfigurationExpression> configure)
2 : this(Build(configure))
3 {
4 }
5
6 private static MapperConfigurationExpression Build(Action<IMapperConfigurationExpression> configure)
7 {
8 var expr = new MapperConfigurationExpression();
9 configure(expr);
10 return expr;
11 }
上面就是构造方法处理的逻辑,实际上就是build方法创造一个MapperConfigurationExpression对象,然后把这个对象传给Action生成Mapper,这个Mapper的意思就是source Type和destination Type的字段映射对象。我们继续查看这个第8行的代码在new这个对象的时候做了什么初始化操作。这个MapperConfigurationExpression继承了抽象类Profile,而在这个父类的构造方法里初始化了IMemberConfiguration类,这个是为了制定map规则对象,默认是DefaultMember这个对象。下面是这个创建完MapperConfigurationExpression后调用构造方法。
1 public MapperConfiguration(MapperConfigurationExpression configurationExpression)
2 {
3 _mappers = configurationExpression.Mappers.ToArray();
4 _resolvedMaps = new LockingConcurrentDictionary<TypePair, TypeMap>(GetTypeMap);
5 _executionPlans = new LockingConcurrentDictionary<MapRequest, Delegate>(CompileExecutionPlan);
6 _validator = new ConfigurationValidator(this, configurationExpression);
7 ExpressionBuilder = new ExpressionBuilder(this);
8
9 ServiceCtor = configurationExpression.ServiceCtor;
10 EnableNullPropagationForQueryMapping = configurationExpression.EnableNullPropagationForQueryMapping ?? false;
11 MaxExecutionPlanDepth = configurationExpression.Advanced.MaxExecutionPlanDepth + 1;
12 ResultConverters = configurationExpression.Advanced.QueryableResultConverters.ToArray();
13 Binders = configurationExpression.Advanced.QueryableBinders.ToArray();
14 RecursiveQueriesMaxDepth = configurationExpression.Advanced.RecursiveQueriesMaxDepth;
15
16 Configuration = new ProfileMap(configurationExpression);
17 Profiles = new[] { Configuration }.Concat(configurationExpression.Profiles.Select(p => new ProfileMap(p, configurationExpression))).ToArray();
18
19 configurationExpression.Features.Configure(this);
20
21 foreach (var beforeSealAction in configurationExpression.Advanced.BeforeSealActions)
22 beforeSealAction?.Invoke(this);
23 Seal();
24 }
在这个构造方法里会将之前创建的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中。
1 public LambdaExpression CreateMapperLambda(HashSet<TypeMap> typeMapsPath)
2 {
3 var customExpression = TypeConverterMapper() ?? _typeMap.CustomMapFunction ?? _typeMap.CustomMapExpression;
4 if(customExpression != null)
5 {
6 return Lambda(customExpression.ReplaceParameters(Source, _initialDestination, Context), Source, _initialDestination, Context);
7 }
8
9 CheckForCycles(typeMapsPath);
10
11 if(typeMapsPath != null)
12 {
13 return null;
14 }
15
16 var destinationFunc = CreateDestinationFunc();
17
18 var assignmentFunc = CreateAssignmentFunc(destinationFunc);
19
20 var mapperFunc = CreateMapperFunc(assignmentFunc);
21
22 var checkContext = CheckContext(_typeMap, Context);
23 var lambaBody = checkContext != null ? new[] {checkContext, mapperFunc} : new[] {mapperFunc};
24
25 return Lambda(Block(new[] {_destination}, lambaBody), Source, _initialDestination, Context);
26 }
后来的调用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 ...
- HashMap与TreeMap源码分析
1. 引言 在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...
- nginx源码分析之网络初始化
nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...
- zookeeper源码分析之五服务端(集群leader)处理请求流程
leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...
- zookeeper源码分析之四服务端(单机)处理请求流程
上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...
- zookeeper源码分析之三客户端发送请求流程
znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...
- java使用websocket,并且获取HttpSession,源码分析
转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...
- ABP源码分析二:ABP中配置的注册和初始化
一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...
随机推荐
- Arduion学习(一)点亮三色发光二极管
这是我接触Arduion以来第一个小实验 实验准备: 1.查阅相关资料,了解本次实验所用到的引脚.接口的相关知识. 2.准备Arduion板(本次实验所用到的型号为mega2560).三色发光二极管. ...
- LeetCode 034 Search for a Range
题目要求:Search for a Range Given a sorted array of integers, find the starting and ending position of a ...
- Generalizing from a Few Examples: A Survey on Few-Shot Learning 小样本学习最新综述 | 三大数据增强方法
目录 原文链接:小样本学习与智能前沿 01 Transforming Samples from Dtrain 02 Transforming Samples from a Weakly Labeled ...
- fist-第五天冲刺随笔
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...
- SQL Server 索引碎片整理
索引碎片整理的四种方法: 1)删除索引并重建 2)使用 DROP_EXISTING 语句重建索引 3)使用 ALTER INDEX REBUILD 语句重建索引 4)使用 ALTER INDEX RE ...
- docker数据持久化/共享---volume,bind-mount,tmpfs-mount
一.将Docker数据挂载到容器 在Docker中,要想实现数据的持久化(所谓Docker的数据持久化即数据不随着Container的结束而结束),需要将数据从宿主机挂载到容器中.目前Docker提供 ...
- DFS,BFS 练习(深搜,广搜,图,leetcode)
https://leetcode-cn.com/problems/route-between-nodes-lcci/ 节点间通路.给定有向图,设计一个算法,找出两个节点之间是否存在一条路径. 示例1: ...
- 蒲公英 · JELLY技术周刊 Vol.33: 前端基础课堂开课啦~
蒲公英 · JELLY技术周刊 Vol.33 页面文件太大?图片过大了吧:页面加载白屏?很有可能是字体文件还没加载完:页面加载时间过长?多半是主进程被阻塞--该怎么办呢?快来小葵,咳咳,「蒲公英」前端 ...
- 团队作业5_测试与发布(Alpha版本)
Alpha版本测试报告 1.测试找出的bug(N个): (1)修复的Bug:很多个,主要是一些疏忽造成的,比如请求url写错导致数据加载不了.比较有意义的bug是因为使用redux,但是用户刷新后数据 ...
- 【Home Page】本博客使用指南
[关于] 坐标:ZJ.HZ.XJ. 高一现役 OIer,经常被吊打. Luogu:_Wallace_ [近期] 浙大 ICPC-ACM 2020 部分题解: 关键字「ZJU-ICPC Summer T ...