最近有一个小项目需要提供接口给第三方使用,接口会得到一个大的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的源码分析的更多相关文章

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

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

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

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

  3. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  4. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  5. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  6. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  7. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  8. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  9. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

随机推荐

  1. 虚拟机VM15 Ubuntu18.04写第一个c程序并实现ssh连接

    输入"su",再输入密码进入根用户 1.开启ssh服务 /etc/init.d/ssh start 若没有安装会出现: (1).安装ssh apt-get install open ...

  2. VC与VB

    VB调用VC dll的返回方式 第一种类型:数值传递注意:在VB中,默认变量传递方式为ByRef为地址,而传递值就是用ByVal,还要注意在C++中,int类型的变量是32位的,在VB中要用long型 ...

  3. day102:MoFang:后端完成对短信验证码的校验&基于celery完成异步短信发送&flask_jwt_extended&用户登录的API接口

    目录 1.用户注册 1.后端完成对短信验证码的校验 2.基于celery实现短信异步发送 2.用户登录 1.jwt登录验证:flask_jwt_extended 2.服务端提供用户登录的API接口 1 ...

  4. 解决调用WebService报基础连接已经关闭: 服务器关闭了本应保持活动状态的连接的错误的方法

    问题可能原因之一:网速的快慢,我经过测试,如果外网访问的话网速慢就是出现此类问题,但是我没有精确测出当在网络流量最低在什么情况下可以避免此类问题问题可能之二:程序发布之前没把原引用的web servi ...

  5. 第8.13节 Python类中内置方法__repr__详解

    当我们在交互环境下输入对象时会直接显示对象的信息,交互环境下输入print(对象)或代码中print(对象)也会输出对象的信息,这些输出信息与两个内置方法:__str__方法和__repr__方法有关 ...

  6. Python字符串学习相关问题

    Python中format_map与format字符串格式化的区别 Python中使用f字符串进行字符串格式化的方法 Python中使用百分号占位符的字符串格式化方法中%s和%r的输出内容有何不同? ...

  7. PyQt(Python+Qt)学习随笔:QListView的viewMode属性

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QListView的viewMode属性用于控制QListView的视图模式,该属性类型为枚举类型Q ...

  8. PyQt(Python+Qt)学习随笔:Qt Designer中Action关联menu菜单和toolBar的方法

    1.Action关联菜单 通过菜单创建的Action,已经与菜单自动关联,如果是单独创建的Action,需要与菜单挂接时,直接将Action Editor中定义好的Action对象拖拽到菜单栏上即可以 ...

  9. sql绕过2

    0x00 sql注入理解 SQL注入能使攻击者绕过认证机制,完全控制远程服务器上的数据库. SQL是结构化查询语言的简称,它是访问数据库的事实标准.目前,大多数Web应用都使用SQL数据库来存放应用程 ...

  10. 分布式存储系统-HDFS

    1 HDFS 架构 HDFS作为分布式文件管理系统,Hadoop的基础.HDFS整体架构包括:NameNode.DataNode.Secondary NameNode,如图: HDFS采用主从式的分布 ...