热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

AutoMapper的源码分析

最近有一个小项目需要提供接口给第三方使用,接口会得到一个大的XML的字符串大约有8个对象100多个字段,在映射到Entity只能通过反射来赋值避免重复的赋值,但是明显感觉到性能下降

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

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

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

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

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

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

 1   public MapperConfiguration(Action configure)
 2             : this(Build(configure))
 3         {
 4         }
 5 
 6         private static MapperConfigurationExpression Build(Action 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(GetTypeMap);
 5             _executiOnPlans= new LockingConcurrentDictionary(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 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运行速度快的原因。这个项目挺小的并且代码工整可读性挺强的,希望大家在阅读源代码能够学的更多。最后谢谢大家。


推荐阅读
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • MyBatis错题分析解析及注意事项
    本文对MyBatis的错题进行了分析和解析,同时介绍了使用MyBatis时需要注意的一些事项,如resultMap的使用、SqlSession和SqlSessionFactory的获取方式、动态SQL中的else元素和when元素的使用、resource属性和url属性的配置方式、typeAliases的使用方法等。同时还指出了在属性名与查询字段名不一致时需要使用resultMap进行结果映射,而不能使用resultType。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
author-avatar
陈协莹隆心
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有