• 【框架学习与探究之依赖注入--Autofac】


    声明

    本文欢迎转载,原文地址:http://www.cnblogs.com/DjlNet/p/7603642.html


    同样的又是一个双11如期而至,淘宝/天猫实时数据显示,开场3分钟总交易额突破100亿元人民币,简直可怕!同时产生了新的支付峰值诞生:25.6万笔/秒,以及数据库处理峰值4200万次/秒,说这些不是再给某某打广告哈,只是感叹如今的技术和业务双向驱动所带来的巨大冲击力,完成了史上基本不可能的事情,相信这绝不是极限,因为中国女人的支付能力是木有上限一说的,开玩笑啦,哈哈哈,好了废话说了一通,趁着博主也完成剁手之后,然后赶紧回到了继续学习的旅途中......


    前言

    当看到这文章题目的时候,相信很多人都是感叹,怎么又是一个来讲依赖注入,怎么又是autofac浅析之类的,所以咯,此文只是博主阅读文档和平时的经验整合,然后呐,懂的人或者熟悉的中高配玩家,就可以绕道了,╮(╯﹏╰)╭,其实博主之前的个人项目还是公司项目都是采用 Unity 来作为 ioc 套件进行使用,使用过程也还算良好也是基本具备了一个ioc组件应该具有的价值,或许也是微软企业套件为数剩下来不多的好的组件了吧,但是无奈后期博主觉得Unity在性能或者灵活性上面逐渐展示出来的欠缺性,相比于今儿我们熟知的autofac而言就越显尴尬,当然二者的官方文档都还算字字走心还算详细,但是重要的一点就是autofac在社区的热度以及地位就比较高了,其次就是它的周边扩展也是相当多的支持,当然啦,从一个系统或者项目俯视来看,它或者谁都是系统组件的一个冰山一角,都是作为一个零件使用,但是我们依然不能就此忽略其的地位,因为它算是在一个系统中处于贯穿的层次级别,所以在对象创建和依赖+生命周期统筹上面起到了决定性作用,所以各位同学当然也得需要重点关注如何集成才是......


    DIP、Ioc和DI概念(理解)

    首先按照国际惯例给出各自的wiki,Dependency Injection (DI) Wiki : https://en.wikipedia.org/wiki/Dependency_injection ;Inversion of control (IOC) Wike : https://en.wikipedia.org/wiki/Inversion_of_control ; 这里给出的是英文版解释,当然英语能保留原滋原味,不过我相信中文的解释更能让人接受一些,所以这里博主依然找出了中文相关的通俗解释,这里引用一篇好文当中的话(原文链接:http://www.cnblogs.com/liuhaorain/p/3747470.html);
    1、依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念)。
    2、控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式)。
    3、依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
    4、IoC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。
    至于每个概念的解读到理解以及示例展示,就可以看上文给出的连接,文中的解析挺到位的,所以这里备份链接地址在文中惠存,文中有相关的通俗解释,可以加深影响后加以理解,内化之....


    Autofac文档摘取

    当然也是因为 autofac 本身文档完备性,相信也是大部分玩家选择用它的原因之一,简直感觉加一起来就是一本薄的书了哈,所以这里博主先后阅读了两三遍之,采用了一遍走马观花有个印象,二遍择优而读加深理解,三遍查漏补缺,可能很多人都觉得哪有这么麻烦,先用起来到时候有问题有不懂的地方再去翻查文档即可,当时候博主也是准备这样的来的,其实方式并没有好坏一说,只是看个人喜好和学习方法而已,博主打算在完成此框架的学习之后,就着手与案例分析和框架集成的工作了,其实先前早些时候已经尝试过了对通用开发框架的一些实践,不过现在看看有些愣之嫩之,所以在此对开发当中需求的框架基本过一遍之后心中有谱儿之后,再次做集成工作就会显得“稳”一些,上述是博主自己的想法......2333333


    关于注册那点事儿

    看来都是通过一个主要对象ContainerBuilder完成对各式各样的组件注册,且通过名字也可以知道此类是生成服务容器的生成器在生成容器之前,自然就是注册各种相关组件或者服务啦,关于API注册相关的话,大致有以下相关主要方式注册,不限于此:
    1、通过类型注册( Register by Type ),类似:builder.RegisterType();builder.RegisterType(typeof(NLogLogger));,同时这里当使用该注册件时,会通过反射实例化对象,且会选择最合适的构造函数使用会自动传递依赖项进去,当然官方也提供了builder.RegisterType().UsingConstructor(typeof(ILogger), typeof(IConfigReader)); 指定构造函数使用,这里最合适体现在该组件的依赖项在容器的是否存在而定

    2、实例对象注册( Instance Components ),Demo代码所示:var output = new StringWriter();builder.RegisterInstance(output).As().ExternallyOwned(); , 其中**方法 ExternallyOwned 可以忽略容器对 实例对象的释放控制,同时也可以注册 单例对象 使用,且需要注意注册单例实例对象释放应该由开发人员控制而不是容器 **。

    3、Lambda 表达式组件注册 ( Lambda Expression Components ),builder.Register(c => new A(c.Resolve()));,其中表达式的灵活性和性能优化,是官方推荐的注册方式,分别体现在 利用表达式可以自定义解析依赖项和传递需要的构造函数( 也包含注册依赖的属性和根据参数自定义注册流程 )等等,其次就是表达式对于性能有一定的提升相对于反射而言。

    4、泛型组件注册 (Open Generic Components):

        builder.RegisterGeneric(typeof(NHibernateRepository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();
    

    这个便是我们经常使用的API了,也就是我们对仓储服务的注册,但是我们实体有很多,所以这里就 需要泛型注册可以批量注入到容器中去,而且需要单独对某一个具体类型例如 NHibernateRepository 注册就会覆盖泛型的注入,同理解析服务的时候也会实例具体对象的服务实例。

    其他备注注意事项:
    关于 组件与服务 的注意点:当一个组件如下注册为服务的时候 :

        builder.RegisterType<CallLogger>().As<ILogger>().As<ICallInterceptor>();
    

    如果 需要解析组件本身需要加上AsSelf() ** ,关键的 AsSelf 来表示,不然注册为服务会覆盖掉组件本身的注册结果。
    默认注册情况下, 多个组件注册同一个接口,以最后一个为准,除非使用 Named 支持多组件同一个服务注册关系,且如果使用API:PreserveExistingDefaults ,可以是的第一个注册为默认项。
    条件注册:主要API,
    OnlyIf** 方法提供Lambda表达式表示指定条件下发生,IfNotRegistered 方法同理注定没注册条件下发生。
    参数传递给组件作为注册需求 ,通过 表达式注册或者显示使用 WithParameter 方法传递即可,同样也可以在解析的传递参数也是可以的。
    属性注册:通过表达式或者利用OnActivated事件来主动解析,如果注册的是反射组件需要使用 PropertiesAutowired 来突出属性的注入,同样提供了 WithProperty 来主动注入对应属性名的依赖项。
    程序集扫描注册:RegisterAssemblyTypes 可以自定义扫描程序集逻辑指定类型注册,可以使用 Except 排除不需要的类型,AsImplementedInterfaces 注册为类型对应的公开接口服务类型,RegisterAssemblyModules 注册自定义module,实现批量套件式注册,注意如果宿主程序使用iis,如果使用iis回收,触发程序集重载,需要使用 var assemblies = BuildManager.GetReferencedAssemblies().Cast();获取最新加载的程序集扫描


    关于解析那点事儿

    解析主要的方法便是 Resolve ,解析可以通过 NamedParameter 、TypedParameter 、ResolvedParameter 三种方式传递解析组件所需要的参数依赖,关于容器中的不同关系类型的不同解析方式有所不同,具体参考:http://autofac.readthedocs.io/en/latest/resolve/relationships.html#supported-relationship-types,其实官方提供这么多重载或者API函数只是为了广大玩家的易用性,至于平时开发过程中,使用的API数量相对来说还是有限的,那博主自身情况设定,直接依赖和延迟实例化对象用到过了,还有一点:关于可枚举对象服务组件的注册,也就是一个接口多个实现类,解析接口默认如果没有注册回抛出异常,但是如下方式则是为empty,scope.Resolve<IEnumerable>();


    控制作用域和组件存活生命周期

    根据官方的说法,其中有两点是值得开发人员注意的:
    1、生命周期范围是可嵌套的,它们控制组件如何共享。例如,单例服务可能从根生存期范围解决,而单个工作单元可能需要自己的其他服务实例,你可以通过在注册时设置其实例范围来确定组件的共享方式。例如 UserService 需要 RoleRepository 需要它们的scope处于处于同一个层次
    2、using scope会主动追踪范围内组件,并在释放范围内组件在scope结束的时候。例如,如果您有一个实现了IDisposable的组件,并且您可以从scope范围内解析出来,则该scope范围将为你处理该对象的释放工作,自动处理让使用者不必知道底层实现。但是如果不需要自动管理,你可以控制此行为或添加新的处置行为。

    始终从scope范围内解析服务而非根容器,这一点很重要。由于生命周期范围的跟踪特性,如果您从跟容器中解析了大量的一次性组件,则可能会无意中造成内存泄漏,因为根容器将持有对那些一次性组件的引用(通常是应用程序的生命周期)。如果Autofac检测到单例或共享组件的使用情况,它会自动将其放入适当的跟踪范围。所以上诉的情况,可以理解为尽量使用自建scope来解析服务组件,而跟容器就应该注册类似单例服务,与容器和应用程序本属于一个生命周期的东西

    官方例子很好地说明了,在一个web应用程序中应该如何分配组件注册的生命周期的选择问题:
    现在应用程序具有一个全局单例日志记录服务,然后两个同时请求进入Web应用程序,每个请求都是一个逻辑的“工作单元”,每个请求都需要自己的订单处理服务,每个订单处理服务都需要将信息记录到日志服务中。在这种情况下,拥有包含单例记录服务的根生存期范围,并且每个请求都有一个子生命周期范围child scope,每个范围都有自己的订单处理服务:

    +---------------------------------------------------+
    |                 Autofac Container                 |
    |                Root Lifetime Scope                |
    |                                                   |
    |                  Logging Service                  |
    |            (shared across all requests)           |
    |                                                   |
    | +----------------------+ +----------------------+ |
    | |  First Request Scope | | Second Request Scope | |
    | |                      | |                      | |
    | |   Order Processor    | |   Order Processor    | |
    | +----------------------+ +----------------------+ |
    +---------------------------------------------------+
    

    当每个请求结束时,请求生存期范围结束,相应的订单处理器被释放(如果实现了IDisposable)。Log日志记录服务作为一个单例,在将来的请求中保持共享。

    关于具体注册的时候可供选择的实例生命周期范围,默认情况下是使用的:InstancePerDependency 也就是瞬态,每次解析都是一个新的对象,SingleInstance 单例不多说,InstancePerLifetimeScope scope范围内共享实例,通过 BeginLifetimeScope 即可建立一个scope范围,InstancePerMatchingLifetimeScope 指定具体name的scope范围内实例对象共享,其中pre request scope就是利用这道理实现了每次请求一个独立的作用域,InstancePerRequest web应用单次请求组件单例,注意如果木有请求的环境下,解析组件会抛出异常,InstancePerOwned 基本用不到,Thread Scope 参照线程启动的时候构建InstancePerLifetimeScope类型的作用范围曲线救国就可以了

    关于服务依赖导致各自依赖项长期存活问题,一般情况下,应用程序中,上层服务组件所需要依赖的下层服务的生命周期要小于等于上层服务的生命周期即可,这样就可以正确的表示依赖关系和隶属关系,但是得除开单例情况,因为它是贯穿了整个应用生命周期的存在。

    关于组件对象的释放问题,一般的ioc容器包括autofac都已经实现了自动处理释放问题,根据不同的注册生命周期选择,则会自动释放,不需要开发者关系,对象残留的问题。如需要手动释放其他自定义非托管资源可以利用 OnRelease event来自定义处理逻辑,注意这会覆盖base逻辑,所以如果本身组件实现了idisposable就要先调用base.Diposable()方法


    注册配置来源

    这里博主还是推荐代码注册,因为配置文件的管理也是挺麻烦的,除非需要再上线之后需要动态调整一下注册参数或者生命周期的选择等等,那就需要xml json等注册方式,一般情况不会自定注册源,且autofac module 的方法很好划分了大量组件分类注册的问题。


    各种应用程序集成

    这里直接复制粘贴官方对每个应用的demo代码即可,思路都是替换既有系统的默认的注册依赖组价,基本上已经足够我们的使用,到时开发的时候直接参考sample即可
    这里官方给出了最佳实践的一些开发方式或者注意点:http://autofac.readthedocs.io/en/latest/best-practices/index.html 我这里就不翻译,相信大家都看得懂


    小总结

    本来这篇水文在早些时候都应该要完成的,拖拖踏踏到现在,连广州都已经也到了冬天了,好冷......,还好有防寒服加持,至此,差不多主要博主想研究的框架都差不多了,至于说net core相关的也在偶尔持续关于 github issues 的动态,毕竟官方给了road map ,跪着也要实现不是嘛!接下来,博主就要基于实际应用开发集成各式组件,构建属于自己的单体应用架构,毕竟走向分布式的前提是,单体架构不够使用的情况下,当然后面设计的时候会相对引入一些ddd的相关思想,但并非全部,因为要基于显示考虑问题才是嘛......未完待续!

  • 相关阅读:
    word批量打印工具,c#写的
    word添加页眉脚和设置各页不同的页眉页脚.
    打印机双面打印
    ORACLE OCP认证
    基于.net程序,使用cefsharp开发的打开网页工具,如何不加载图片
    在iis上运行的服务器端程序,运行一段时间后,访问都只出现一行乱码,回收进程池后又好了,求大神回复
    ArcGis API for JavaScript 开发笔记一 加载地图
    修改现有消息类让.net core项目支持Protobuf
    结合现有分布式系统的数据一致性思考
    让现有vue前端项目快速支持多语言
  • 原文地址:https://www.cnblogs.com/DjlNet/p/7603642.html
Copyright © 2020-2023  润新知