原文链接: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.