【译】Dependency Injection with Autofac

时间:2021-06-17 03:30:22

先说下为什么翻译这篇文章,既定的方向是架构,然后为了学习架构就去学习一些架构模式、设计思想。

突然有一天发现依赖注入这种技能。为了使得架构可测试、易维护、可扩展,需要架构设计为松耦合类型,简单的说也就是解耦。为了解耦前面的人提出各种理论,主要思想是控制反转,而现在主流的主要是两个:依赖注入、服务定位(有篇英文文章特意讨论这种模式,最终的结论是否定的,乍看了一眼,没看懂)

有了某个思想便可以在程序中体现,用多了,人们就去对它进行封装,普通的人封装的大概就自己用,高人封装了便成了组件。现在基本上都在弄 .net ,所以说的技术也基本上是这一块的。.net 的 IOC 框架有 Autofac、Castle Windsor、Unity、Spring.NET、StructureMap、Ninject。

有这么多框架为什么选 Autofac,我在这个地方只是学习这种 IOC 思想的框架,并没有真实的用在生产中。Autofac 各方面的的性能都比较中庸,所以用它作为 IOC 入门应该是比较合适的,这样也就导致这篇文章适合初学者阅读。

接下来就要具体说下翻译的原文,文章对于 Autofac 在 IOC 思想的实现和 Autofac 如何使用做了十分透彻的说明,但是本人水平有限,一些地方没有翻译对,甚至翻译错了,这些我特意指明,省的坑了各位,如果发现不对直接参考原文,但也请告知我,以便改正。

原文地址:http://www.codeproject.com/Articles/25380/Dependency-Injection-with-Autofac

----------------------------------------------------------------------------------------------------------------------------------------------

Dependency Injection with Autofac

使用 Autofac 进行依赖注入

目录

Introduction-介绍

Autofac 是发布在 Google Code 上的依赖注入或控制反转的开源容器。

Autofac 与许多类似技术不同的是它坚持尽可能的使用纯质的 C# 语法。【也就说不去依赖其它的组件,从而使得 Autofac 有着更好的兼容性】

等等

The Example Application-应用程序示例

这个程序是一个控制台程序,它检查一个备忘录列表,每一个备忘录有一个过期日期,程序向用户提醒哪些过期的备忘录。

Checking for Overdue Memos-检查过期备忘录

以下几点作为使用依赖注入的直接原因:

1 以参数接收所有依赖的对象

2 独立的持久化-IQueryable 对象可能是数据库表、一个结构文件,再或者内存集合。(集合公布)

3 提醒用户的形式是独立的。(以接口控制)

这些举措使得这个类容易测试,可配置的并且是可维护的。

Notifying the User-提醒用户

Data Storage-数据仓储

IQueryable 接口在 dotnet 3.5 中引入,它适合作为备忘录的数据源。因为它可以同时用于内存对象和关系型数据库的查询。

Wiring Up Components-连接组件

应用程序最基础的结构如下:

本文主要关注的是 MemoCHecker 如何获取与它关联的 notifier 和 memo 服务,以及每个被依赖的对象如何获取他们的实体。【有个反转的概念】

Dependency Injection by Hand-手动依赖

更重要的是,它很难去切换不同的实现服务;比如可能用 EmailNotifier 替换 printing notifier,但是原有的会有依赖,或许不同来源于这些 PrintingNotifier【不理解】,但也有可能是与依赖的其它组件有交互。(这也可能是这些组合现成的问题,而它本身就值得写文说明)

Autofac 和其它的依赖组件通过在配置时“扁平化”深度网状对象图。【待译】

Dependency Injection with a Container-容器依赖

当使用 Autofac,访问 MemoChecker 是分离的来源于以下创建方式:

Container.Resolve() 执行请求一个 MemoChecker 的实例,它负责实例的实例化和准备使用。那么,容器是如何工作的、如何创建 MemoChecker 实例。

Component Registrations-注册组件

依赖注入容器是一个把服务映射到组件的集合。在这种情形下,服务是用来识别某一特定的功能,它可以是一个文本标签,但其通常是某个接口。

注册器在系统内部捕获组件动态的行为。其最受关注点是如何创建组件的实例。

Autofac 能够接受创建组件的注册方式有:表达式、提供实例和基于 System.Type 反射。【待译】

Registering a Component Create with Expression-使用表达式注册组件

下面就是为 MemoChecker 组件设置一个注册器:

每一条 Register() 语句仅只处理那些处于对象图顶端的且其直接关联到依赖对象上。

C=> new MemoChecker(…) 将被用于容器创建 MemoChecker 组件。

每个 MemoChecker 依赖于额外的两个服务:IQueryable<Memo> 和 IMemoDueNotifier 。这些服务通过容器调用 Resolve() 方法在 lambda 表达式内检索到,容器是通过参数 c 传递过来的。

上面的注册器并没有对 IQueryabl<Meno> 和 IMemoDueNotifier 的实现进行任何说明。这两个服务的配置依赖关系同 MemoChecker 的配置类似。

上面的表达式将被提供于 Register() 当其返回 MemoChecker 类型,于是,Autofac 会将其作为这个注册器的默认服务,除非有另外的更为准确的 As() 方法,下面这个 As() 方法包含更为明确的目的:

无论哪种方式,某个 MemoChecker 实例的请求都将是调用我们的表达式后的某一结果。

Autofac 不会在组件注册时执行表达式,相反,它会等到 Resolve<MemoChecker>() 调用时执行表达式。这一点很关键,因为它除去了组件注册的顺序的依赖。【依赖关系不受注册顺序影响】

Registering a Component Instance-使用实例注册组件

IQueryable<Memo> 服务由存在的 memos 实例提供,PrintingMemoNotifier 类最终指向 TextWriter 的实例 Console.Out 。

Memos 和 Console.Out 都以已经创建的实例提供给容器。(对于 ExternallyOwned() 的解释,请参考 Deterministic Disposal)

Registering a Component with its Implementation Type-使用实现类型注册组件

Autofac 也可以用其它容器创建组件的方式-反射来创建组件。(更多这个场景的优化伴随着 MSIL-generation)

这种方式的含义是说你可以告诉 Autofac 有关于提供服务的类型,并且容器将会使用最合适的构造函数,以及根据其它可用的服务来选择参数。

MemoChecker 注册可以被下面的形式替换:

一般说来,最常见的做法是使用自动连接去注册某一批次的组件。【待译】

这种方式使得大量的组件可以使用但没有繁重的注册每一个组件的消耗,并且你需要清楚的思考这种情形。Autofac 提供如下快捷的方式批次注册组件:

自动连接在以程序 XML 配置文件注册组件时也特别实用。

Completing the Example-完整示例

在请求 MemoChecker 服务之前程序创建注册组件如下所示:

在上面的配置代码中没有嵌套显示了“扁平”的依赖结构而这些由容器提供。

这似乎很难看到上面这种注册方式比手动的例子更为之间的对象结构,但请再次回忆起,这个示例比平常的系统拥有更少的组件。

最为重要的区别是现在每个组件的配置都独立于所有其他的组件。伴随着更多的组件添加到系统中,他们可以被理解为纯净的按照这些服务所暴露的河这些服务所需要的。这是一种有效的控制架构复杂化的手段。

Deterministic Disposal-指定清理

IDisposable 兼具祝福和诅咒。组件有一致的方式去同其应当被清理交互这是好的。不幸的是,哪个组件在什么时候应当被清理并不总是很容易确定。

这个问题由于允许相同的服务有不同的实现而变得糟糕。在这个示例中,IMemoDueNotifier 有许多不同的实现可能被部署。其中的一些将有工厂创建,一些将会是单例的形式,一些将会被清理,还有一些将不会被清理。

组件作为一个通告者是没有办法决定它们应当尝试把它丢给 IDisposable 和调用 Dispose() 或者是不这样做。各种记录的结果是导致易错的河繁琐的。

Autofac 使用通过容器跟踪创建的所有可以清理对象方式解决这个问题。记录的示例如下:

容器在一个 using 程序块中,因为它拥有所有它创建的组件的所有权,并且清理它们当容器被清理的时候。

这一点很重要因为它真正实现了从配置关注分离的精神【待译】,MemoChecker 服务可以在任何需要它的时候使用,甚至是以另外被依赖的组件角色被直接创建,不用担心它们是否应当被清理。

伴随着这个带来的内心的平静,你甚至不用来回读示例程序来发现任何需要实现 IDisposable (实际是没有)的类,因为你可以依赖容器去做正确的事情。

Disabling Disposal

记住 ExternallyOwned() 子句在上面完整的示例中添加到 Console.Out 的注册上。这是合意的因为 Console.Out 是可清理的,但这个时候容器不应当去清理它。

Fine-Grained Control of Component Lifetimes-精细控制组件生命周期

容器将会正常的存在于应用程序执行期间,并且在相同应用程序长生命周期内清理它是一个很好的方式去释放组件所拥有的资源。大多数不平凡的程序也应释放资源在一些其它的时候,如:Http 请求完成、工作线程退出或者一个用户会话结束。

Autofac 使用嵌套的生命周期域帮助你管理这些生命周期,代码如下:

生命周期管理是通过注册组件实例映射到生命周期域实现的。

Component Lifetime-组件生命周期

Autofac 允许你指定一个组件多少实例可以驻留以及它们将如何在其它组件间共享。

控制组件独立的定义作用域是一个非常重要的改进对于传统方法使用静态 Instance 属性来定义单例。这个区别在于对象是什么和如何使用对象。【待译】

使用 Autofac 最常用生命周期设置如下:

单例

每一依赖一个实例

每一生命作用域一个实例

Single Instance-单例

单例生命周期,它将是大多数组件只有一个实例在容器中的选择,并且实例会随着创建它的容器清理时被清理。

一个组件可以使用 SingleInstance() 修饰配置为这种生命周期,如下所示:

每次这样的组件从容器请求时,都会返回相同的实例:

Instance Per Dependency-每一依赖一个实例

当组件注册时没有特意指定生命周期,将会默认 instance-per-dependency 生命周期。每次从容器获取这样的组件时,都会返回新建的实例:

某个使用这种生命周期的组件将会跟随着组件被创建的生命周期而被清理。如果一个 per-dependency 组件被请求是去构造一个 single-instance 组件,对于这种例子,那么 per-denpendency 组件将会随着 single-instance 组件对于容器的生命周期一直存在。

Instance per Lifetime Scope-每一生命周期作用域一个实例

这种方式满足每一线程、每一请求或者每一事务组件的生命周期的灵活需求。简单创建一个生命作用域可以生存在必须的生命周期持续的周期。【待译】来自相同的作用域请求将会得到相同的实例,同时来自不同作用域的请求将会得到不同的实例。

Using Scope to Control Visibility-使用作用域去控制组件依赖的可见性

组件间的依赖仅只在其满足其它的组件在其相同的作用域或者在其外部(父)作用域才能建立。这样确保组件间的依赖在其没有建立前被处理掉。【待译】如果 application、session 和Request 有着嵌套的需求,那么可能会按照如下的方式创建:

在这个示例中请知道,appContainer 将会创建许多子 sessionLifetime,并且每个 session 同appContainer 一样会有许多子 controller。【待译】

在这个场景,允许依赖的方向是 Request -> session -> application。用于处理用户请求的组件可以引用任何其他的组件。但是依赖关系在相反的方向上是不被允许的,所以,对于这个情况,应用程序级别的 single-instance 组件将不会连接到指定单例用户 session 的组件。【待译】

在这样的层次结构中,Autofac 总是服务于 shortest-lived 生命周期的组件请求。这将会平常的请求生命周期。【待译】Single-instance 组件将会驻留在应用程序级别,来将组件的生命周期关联到 session 级别,详情请参考 wiki。【待译】

Autofac 的作用域模式是灵活和有效的。作用域和嵌套生命周期间的关系使得注册庞大的依赖成员成为可能。

Autofac in Application-在程序中使用 Autofac

依赖注入是一种极其强大的结构控制机制,但是想要获取这些优势,系统的组件通过容器成为其它组件能用的占比十分重要。

到目前为止 Autofac 所描述的特征都是被设计为获取存在的,当“老的简朴的” .Net 组件添加到容器中,不需要修改或适配代码。

Expressive Registrations-显示注册

使用表达式来向容器注册组件在使用 Autofac 的应用程序中。一些示例场景说明这些由 Autofac 实现的:

已经存在的工厂方法可以用表达式的形式使用:

已经存在需要在第一次访问时加载的单例可以使用表达式注册,并且它的加载是延迟的:

传递给组件的参数可以使任何的来源:

某个类型的实现甚至可以根据参数来决定:

Simplified Integration-简单集成

集成,在这里的意思是使得已存在的类库服务和程序组件通过容器使他们可以使用。

Autofac 支持一些典型场景的集成比如在 ASP.Net 应用程序中使用的那样;然而,Autofac 模型的灵活性产生了大量的繁琐的集成工作,因而最好的方式是把这些留给程序设计者在他们程序最合适的地方实现。

基于表达式的注册和指定的清理以及延迟加载组件的方案,可以产生令人惊奇的效果当它们集成在一起时:

这是一个 WCF client 集成自 Autofac 网站的示例。ITrackListing 和 IChannelFactory<ITrackkListing> 这两个关键的服务,WCF 管道对于这些通用的 bit 流很容易基于表达式进行注册。【待译】

一些要点如下:

Channel 工厂只有在其需要的时候创建,但是一旦创建,它将保留以在每一次 ITrackListing 请求时重用。

ITrackListing 不继承自 IDisposable,但在 WCF 中,客户端服务代理以这种方式创建需要联系到 IDisposable 并且清理它。使用 ITRackListing 接口,使得仍然不知道其实现细节。

终端信息可以来自任何地方-任意服务、数据库或者某个配置文件。

除了使用基本的 Register() 方法没有其他概念引入。

这小节向你展示 Autofac 是如何工作的,使得你集中于实现你的应用程序,而不是扩展或惊奇 DI 容器的复杂性。

Where to Next?

我希望这篇文章用来说明来学习如何使用 Autofac 的各种要点,接下来的步骤可能是:

下载源代码

在 Wiki 上阅读更多文章

在论坛里介绍你自己

Credits