[翻译] Autofac 入门文档
原文链接:http://docs.autofac.org/en/latest/getting-started/index.html
在程序中使用Autofac的基本模式是:
- 用控制反转(IoC)的思想组织程序。
- 添加对 Autofac 的引用。
- 程序启动阶段
- 创建 ContainerBuilder。
- 注册组件。
- 生成容器。
- 程序执行阶段
- 从容器创建生命周期范围对象(ILifetimeScope接口)。
- 使用生命周期范围对象解析组件实例。
本文通过一个简单的控制台程序演示这些步骤。
组织程序
控制反转的基本思路是,不让类创建它的依赖项,而是将依赖项通过构造函数传递给它。请参阅 Martin Fowler 这篇解释依赖注入/控制反转的文章。(中文翻译)
在本示例中,我们定义一个输出“今天”的日期的类。但是,我们不想让它和 Console 类捆绑在一起。这有两个原因:1,方便测试,2,在没有Console的环境中也能使用。
我们还对输出日期的方式进行抽象,这样就可以随时切换到另一个版本,比如输出“明天”的日期。
代码:
using System; namespace DemoApp { // 此接口将“输出”概念从Console类解耦。 // 我们只管“输出”,而不关心操作是怎么进行的。 public interface IOutput { void Write(string content); } // 这里的实现方式是向Console输出内容。 // 也可以用其他方式,比如输出到 Debug 和 Trace。 public class ConsoleOutput : IOutput { public void Write(string content) { Console.WriteLine(content); } } // 这个接口将“输出日期”的概念从具体的输出方法中解耦。 public interface IDateWriter { void WriteDate(); } // TodayWriter 是这些元素汇合在一起的地方。 // 它的构造函数有一个 IOutput 参数,通过提供不同的实现类, // TodayWriter可以将日期写到不同的地方。 // 进一步的,在这里WriteDate的实现方式是输出“今天的日期”, // 我们可以用另一个类输出其他格式,或者其他日期。 public class TodayWriter : IDateWriter { private IOutput _output; public TodayWriter(IOutput output) { this._output = output; } public void WriteDate() { this._output.Write(DateTime.Today.ToShortDateString()); } } }
现在我们有了组织良好的依赖关系,接下来引入 Autofac。
添加对 Autofac 的引用
首先向项目添加对Autofac的引用。这个示例中,我们只使用 Autofac 的核心部分。其他类型的应用程序可能需要Autofac 集成库。
最简单的方式是使用 NuGet。“Autofac” 程序包包含所有核心功能。
程序启动阶段
我们在程序启动时创建 ContainerBuilder 对象,然后向它注册组件。组件可以是表达式,.NET 类型, 或者其他暴露服务的代码。组件可以接受其他依赖项。
假设有下面的.net类型:
public class SomeType : IService { }
有两种使用 SomeType 的方式:
- 作为这个类型本身, 也就是声明为 SomeType
- 作为接口, 也就是声明为 IService
在这里,组件是 SomeType,它暴露的服务是 SomeType 和 IService。
在 Autofac 中, 使用 ContainerBuilder 注册组件:
// 创建builder var builder = new ContainerBuilder(); // 通常仅通过接口暴露服务 builder.RegisterType<SomeType>().As<IService>(); // 但是, 如果同时需要两种服务(不常见),可以用这种方式: builder.RegisterType<SomeType>().AsSelf().As<IService>();
在示例程序中,我们要注册全部组件(类)并暴露他们的服务 (接口),以便他们能连接起来。
我们还需要把容器保存起来,以便稍后使用它解析类型。
using System; using Autofac; namespace DemoApp { public class Program { private static IContainer Container { get; set; } static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<ConsoleOutput>().As<IOutput>(); builder.RegisterType<TodayWriter>().As<IDateWriter>(); Container = builder.Build(); // The WriteDate method is where we'll make use // of our dependency injection. We'll define that // in a bit. WriteDate(); } } }
现在,容器里注册了全部所需的组件和服务,接下来就可以使用容器进行解析。
程序执行阶段
程序执行时,通过从范围(ILifetimeScope)对象解析并使用组件。
容器本身即是一个范围对象,因此可以从容器直接解析类型,但是不推荐这样做。
解析组件时,根据注册时定义的实例范围创建一个新的实例。(解析组件类似于使用new操作符) 有些组件需要清理 (比如实现IDisposable接口) -当范围对象被清理时,Autofac 可以同时清理它所解析的组件。
由于容器对象的生命周期与应用程序相同,如果从容器解析了大量内容, 不被清理的组件会越积越多,造成资源泄露。
相反,我们从容器创建一个范围对象,然后通过范围解析对象,范围对象被清理时,从它解析的组件也一同被清理。
(使用 Autofac 集成库时,子范围通常会自动创建。)
对于本示例,“WriteDate” 方法从范围对象获取对象,使用结束时只要清理范围对象。
namespace DemoApp { public class Program { private static IContainer Container { get; set; } static void Main(string[] args) { // ...the stuff you saw earlier... } public static void WriteDate() { // 创建范围,解析 IDateWriter, // 使用 writer,最后清理范围对象。 using (var scope = Container.BeginLifetimeScope()) { var writer = scope.Resolve<IDateWriter>(); writer.WriteDate(); } } } }
程序的执行过程如下:
- “WriteDate” 方法向 Autofac 请求 IDateWriter 实例。
- Autofac 发现 IDateWriter 映射到 TodayWriter ,于是准备创建一个 TodayWriter 实例。
- Autofac 发现 TodayWriter 的构造函数需要一个IOutput 实例。
- Autofac 发现 IOutput 映射到 ConsoleOutput,于是创建一个ConsoleOutput实例。
- Autofac 使用 ConsoleOutput 实例完成 TodayWriter 的创建。
- Autofac 返回 TodayWriter。
想输出另一个日期时,可以实现另一个 IDateWriter,然后更改启动阶段的注册内容,而不需要更改其他类。这就是控制反转。
注意: 一般而言,普遍认为服务定位是反模式(请参考这篇文章,服务定位器是反模式) 。换言之,随处手工创建范围对象,在代码中零散的使用容器对象是不好的方式(译注:autofac 的范围对象相当于服务定位器)。通过使用Autofac 集成库 ,可以避免示例代码中的使用方式。相反,内容在一个位置集中解析,也就是在程序的 “最顶层”位置,极少需要进行手工解析。
更进一步
示例演示了如何使用 Autofac, 您可以继续了解:
- 查看集成库列表,了解怎样将 Autofac 集成到程序。
- 了解注册组件的方式。
- 了解 Autofac 配置选项,对组件注册进行管理。