[翻译] Autofac 中注册的概念

原文链接:http://docs.autofac.org/en/latest/register/registration.html

所谓注册组件,是指创建 ContainerBuilder 的实例,并告诉它哪些组件暴露哪些服务。

组件可以用反射创建,可以提供已经创建好的对象的实例,还可以用拉姆达表达式创建。ContainerBuilder 有一组 Register 方法来进行装配。

每个组件暴露一到多个服务,这些服务用生成器的 As 方法连接起来。

// 创建生成器,生成器用来注册组件和服务
var builder = new ContainerBuilder();

// 注册暴露接口的类型
builder.RegisterType<ConsoleLogger>().As<ILogger>();

// 注册已存在的对象实例
var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();

// 注册创建对象的表达式
builder.Register(c => new ConfigReader("mysection")).As<IConfigReader>();

// 生成容器,完成注册,准备解析对象
var container = builder.Build();

// 现在可以用 Autofac 解析服务,例如,
// 这行代码将执行拉姆达表达式解析 IConfigReader 服务
using(var scope = container.BeginLifetimeScope())
{
  var reader = container.Resolve<IConfigReader>();
}

反射组件

用类型注册

由反射生成的组件通常按类型注册:

var builder = new ContainerBuilder();
builder.RegisterType<ConsoleLogger>();
builder.RegisterType(typeof(ConfigReader));

使用基于反射的组件时,Autofac 选取可用参数最多的构造函数。比如,一个类有三个构造函数:

public class MyComponent
{
    public MyComponent() { /* ... */ }
    public MyComponent(ILogger logger) { /* ... */ }
    public MyComponent(ILogger logger, IConfigReader reader) { /* ... */ }
}

并使用以下代码注册:

var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>();
builder.RegisterType<ConsoleLogger>().As<ILogger>();
var container = builder.Build(); 

using(var scope = container.BeginLifetimeScope())
{
  var component = container.Resolve<MyComponent>();
}

解析时,Autofac 发现 ILogger 已注册,但 IConfigReader 未注册。于是选取第二个构造函数,因为它是可从容器获取参数最多的构造函数。

重要说明: 通过 RegisterType 注册的组件必须是具体类型。组件可以将抽象类和接口暴露为服务,但不能把抽象类和接口注册为组件。Autofac 要创建组件的实例,抽象类和接口不能实例化。

指定构造函数

注册组件时,通过使用 UsingConstructor 方法并指定构造函数的参数类型列表,可以选择特定的构造函数,这将覆盖自动选择:

builder.RegisterType<MyComponent>()
       .UsingConstructor(typeof(ILogger), typeof(IConfigReader));

注意,解析时必须保证参数可用,否则将出现错误。参数既可以在注册时传递,也可以在解析时传递。

实例组件

可使用 RegisterInstance 方法把预先生成的实例注册到容器:

var output = new StringWriter();
builder.RegisterInstance(output).As<TextWriter>();

由于 Autofac 会自动清理对象,如果想自己控制对象的生命周期,而不是由 Autofac 调用 Dispose,就需要用 ExternallyOwned 方法来注册实例:

var output = new StringWriter();
builder.RegisterInstance(output)
       .As<TextWriter>()
       .ExternallyOwned();

将Autofac 集成到现有程序时,注册实例是一个技巧。组件可能会用到单例模式提供的服务,与其直接引用单件,不如将单件注册到容器:

builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();

这样就消除了组件对单件的引用,改由容器提供。

实例暴露的默认服务是实例的具体类型,参考“服务和组件”一节。

拉姆达表达式组件

反射是创建组件的好方式。但是,如果创建逻辑超出简单的调用构造函数时,反射就不够用了。

Autofac 可以接受一个创建组件的委托或拉姆达表达式:

builder.Register(c => new A(c.Resolve<B>()));

参数 c 是组件上下文(IComponentContext),组件在此上下文中注册。可以用它从容器解析出其他值,来辅助创建组件。应使用组件上下文,而不是闭包来访问容器,这很要紧,只有这样资源清理和嵌套容器才不会出问题。

额外的依赖可以用此上下文参数来满足,例如, A的构造函数需要B类型的参数,并且 B 可能有其他依赖项。

表达式创建的组件暴露的默认服务是从表达式推断出的返回类型。

以下是反射方式不能胜任,但拉姆达表达式工作良好的例子。

复杂参数

构造函数参数不能总是声明为简单常量。与其为使用 XML 配置语法创建特定类型的值而大伤脑筋,不如使用类似的代码:

builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));

(当然, session 过期时间在配置文件中更好 – 这里只是说明个大概)

属性注入

尽管有更好的属性注入方式,仍然可以使用表达式和属性初始化器来组装属性:

builder.Register(c => new A(){ MyB = c.ResolveOptional<B>() });

ResolveOptional 方法尝试解析值,即使服务没有注册,也不会抛出异常。(如果服务已注册但不能正确解析仍然会抛出异常) 这是解析服务的选项之一。

多数情况下不推荐属性注入。如果组件有可选的依赖项,通过空对象模式, 重载构造函数,或构造函数参数默认值等替代方案,就可以用构造函数注入的方式来创建整洁的,“稳定的(immutable)”组件。

通过参数值选择实现类

把组件的创建动作隔离出来后,依赖项的具体类型可以变换,这是一个很大的好处。变换通常在运行时完成,而不单是配置时:

builder.Register<CreditCard>(
  (c, p) =>
    {
      var accountId = p.Named<string>("accountId");
      if (accountId.StartsWith("9"))
      {
        return new GoldCard(accountId);
      }
      else
      {
        return new StandardCard(accountId);
      }
    });

本例,CreditCard 有两个实现类,GoldCard 和 StandardCard,使用哪个类是在运行时由 accountId 决定的。

例子里的第二个参数名为 p,这是可选参数。

解析组件:

var card = container.Resolve<CreditCard>(new NamedParameter("accountId", "12345"));

声明创建CreditCard 实例的委托,使用委托工厂,可以得到更整洁,类型安全的语法。

开放式泛型组件

Autofac 支持开放式泛型类型。使用 RegisterGeneric 生成器方法进行注册:

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

从容器请求匹配的服务类型时,Autofac 映射到等价的闭合版本:

// Autofac 返回 NHibernateRepository<Task>
var tasks = container.Resolve<IRepository<Task>>();

注册的特定服务类型 (如IRepository<Person>)会覆盖开放式泛型版本。

服务和组件

注册组件时必须告诉 Autofac 它暴露哪些服务。默认情况下,暴露的服务是组件自身的类型:

// 服务是 "CallLogger"
builder.RegisterType<CallLogger>();

组件仅可通过它暴露的服务来解析。对于本例来说:

// 没问题
scope.Resolve<CallLogger>();

// 有问题,因为注册时没有将 ILogger 接口设置为组件的服务
scope.Resolve<ILogger>();

可以让组件暴露多个服务:

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

暴露服务后,就可以通过它解析组件。注意,将组件暴露为特定的服务后,默认服务(组件类型)会被覆盖:

// 以下均可工作:
scope.Resolve<ILogger>();
scope.Resolve<ICallInterceptor>(); 

// 但这一行不再有用,因为指定的服务覆盖了组件类型
scope.Resolve<CallLogger>();

使用AsSelf 方法,可在暴露其他服务的同时也将自身类型暴露为服务:

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

这样全部代码都可工作:

// 注册时暴露了合适的服务,因此都起作用
scope.Resolve<ILogger>();
scope.Resolve<ICallInterceptor>();
scope.Resolve<CallLogger>();

默认注册

如果多个组件暴露相同的服务,Autofac 将使用最后注册的组件作为服务的默认提供程序:

builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>();

在此场景中,FileLogger 是 ILogger 的默认组件,因为它是最后注册的。

使用 PreserveExistingDefaults 方法可以覆盖这个行为:

builder.Register<ConsoleLogger>().As<ILogger>();
builder.Register<FileLogger>().As<ILogger>().PreserveExistingDefaults();

这时,ConsoleLogger 将是默认的 ILogger 因为后面注册的 FileLogger 使用了 PreserveExistingDefaults()。

配置

可使用XML 配置或编程配置(“modules”)成组提供注册信息,或在运行时更改注册信息。 Autofac modules 还可实现注册信息动态生成,或实现条件注册逻辑。

动态注册

Autofac modules 是引入动态注册逻辑或简单正交特性的最简单方式。例如,将 log4net logger 实例动态附加到正在解析的服务。

如果需要更加动态的行为,比如添加隐式关联类型支持,请参考check out the registration sources section in the advanced concepts area.

你可能感兴趣的:([翻译] Autofac 中注册的概念)