Autofac依赖注入简要说明(2)——Autofac的常用注入姿势
.Net有官方的依赖注入框架,在Asp.Net Core中默认集成,但是有时候需要一些额外的功能,比如说属性注入,这时候就需要使用一些社区的依赖注入框架,它们提供了更加强大的功能,本文讲的是其中一种,.Net社区最老牌依赖注入框架Autofac
在前一篇文章中,讲到了Autofac的生命周期概念,这一篇讲Autofac的具体使用
在Asp.Net Core中使用Autofac
// Program文件中
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
// Asp.Net Core中注册第三方容器的入口
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
// Startup文件中
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<Worker>().InstancePerDependency();
}
在Asp.Net Core中使用Autofac,需要在Program.cs文件中注入AutofavServiceProvider,向Asp.Net Core注入了Autofac服务提供程序之后,在Startup中添加一个ConfigureContainer方法,入参是Autofac的ContainerBuilder类型,在方法内进行我们需要的依赖注入
Autofac的几种注入方式
类型注册
var builder = new ContainerBuilder();
// 可以通过泛型的方式直接注册对应的类型
builder.RegisterType<ConsoleLogger>();
// 也可以通过typeof运算符得到对应的类型作为参数提供给RegisterType方法,这种方式在注册泛型类型时非常有用
builder.RegisterType(typeof(ConfigReader));
将类型注册到容器后,在对应的类的构造函数中通过注入对应的形参,Autofac会自动注入对应的实例,这里的注册采用的是默认的生命周期,也就是瞬时模式,有关生命周期部分,可以看我的上一篇文章,后面便不再讲生命周期
上面讲了使用容器中注入的类可以通过在具体的类的构造函数中注入对应的形参,当有多个构造函数,每个构造函数参数不同时,Autofac会选择可以从容器中获得最多参数的构造函数进行注入,注意,是容器中可以获得的最多参数的构造函数,而不是参数最多的构造函数
除了使用自动选择的构造函数,还可以指定一个构造函数进行注入
// 通过UsingConstructor指定构造函数中传递的类型,以确定使用与之对应参数的构造函数实例化对象
builder.RegisterType<MyComponent>()
.UsingConstructor(typeof(ILogger), typeof(IConfigReader));
实例注入
有些时候会预先得到一个实例,想将这个实例提供给依赖容器,在注入时提供这个实例,Autofac也提供了对应的实现
// 预先得到一个实例
var output = new StringWriter();
// 通过RegisterInstance将实例注入到容器中,在通过容器获取TextWriter的实例时就会获得到output这个实例
builder.RegisterInstance(output).As<TextWriter>();
Autofac会自己管理实例的生命周期,如果注册为瞬时的,那么这个实例在获取一次后就会被调用其对应的Dispose方法,如果希望自己控制对象的生命周期,在注入时需要跟上ExternallyOwned()方法
var output = new StringWriter();
builder.RegisterInstance(output)
.As<TextWriter>()
// 使用ExternallyOwned方法告知Autofac这个被注入的实例对象的生命周期由自己掌控,不需要自动调用Dispose方法
.ExternallyOwned();
这在系统中已经存在的一些单例需要注入到容器中非常有用
// 将一个单例实例注入到容器中,其他被注入的对象就可以直接获取到这个单例,Autofac也不会释放这个单例
builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();
Lambda表达式注入
反射是一种很好的创建依赖注入的方式,但是有时候需要注入的对象并不是使用简单的无参构造函数实例化一个对象,它还需要一些其他的参数或者动作来得到一个对应的实例,这时候可以使用Lambda表达式注入
// 在容器中注入A,但是A不是使用无参构造函数获得实例的,它使用从Autofac中取出的一个B对象实例作为参数,调用需要一个B对象实例的构造函数
builder.Register(c => new A(c.Resolve<B>()));
这里的c是一个IComponentContext对象,通过IcomponentContext对象可以从Autofac容器中解析出相应的对象,然后作为实参提供给A对象的构造函数
通过Lambda表达式注册的服务的类型是根据类型推断系统推断出来的,如上面的例子,这里注入的就是一个A类型的服务,当通过IOC容器请求A对象实例时,就会根据上面的例子返回一个A对象实例
Lambda表达式在以下场景非常有用
复杂参数
有时候构造函数并不是简单的一个固定参数,而可能是一个变化的情况,如果没有这种方式,可能就需要复杂的配置文件才能完成对应的功能
builder.Register(c => new UserSession(DateTime.Now.AddMinutes(25)));
属性注入
Autofac有更优雅的属性注入,但是直接在构造时给属性赋值也是一种方式
// 给A实例的MyB属性赋一个从容器中解析出来的B实例
builder.Register(c => new A(){ MyB = c.ResolveOptional<B>() });
不推荐使用属性注入,如果可以的话建议使用构造函数参数为属性赋值的方式注入
通过参数值选择实现
通过参数值选择实现能够提供一种运行时选择,它不仅仅是最开始时的参数决定的,这个参数在运行时也是可以改变以返回不同的实现
builder.Register<CreditCard>(
(c, p) =>
{
var accountId = p.Named<string>("accountId");
if (accountId.StartsWith("9"))
{
return new GoldCard(accountId);
}
else
{
return new StandardCard(accountId);
}
});
虽然可以这样实现,但是还是建议使用工厂模式,通过传入工厂委托的方式获取不同的实例
泛型注入
Autofac支持泛型注入,据我了解的IOC容器都支持泛型注入,这里的泛型注入指的是Autofac支持以特别的语法强调特别的泛型,它的优先级比默认的泛型高,但是性能没有默认的泛型好,因为不能缓存,这里就不介绍了,如果有需要可以看看官方文档,本文也大多参照Autofac的官方文档
// IOC容器获取IRepository<T>类型的实例时容器会返回一个NHibernateRepository<T>实例
builder.RegisterGeneric(typeof(NHibernateRepository<>))
.As(typeof(IRepository<>))
.InstancePerLifetimeScope();
注入多种类型
通过Autofac注入的对象,如果没有特别指明,那么请求时只需要通过容器请求对应的类型就可以了,这是Autofac的默认行为,其实IOC容器都是这个行为
有时候希望一个实例对应很多接口,通过不同的接口请求到的实例都是同一个实例,那么可以指定实例对应的类型
// 通过ILogger和ICallInterceptor得到的都是CallLogger实例
builder.RegisterType<CallLogger>()
.As<ILogger>()
.As<ICallInterceptor>();
要注册多个接口,必须这个实现类继承了这些接口
多个服务注入的选择
如果一个类型进行了多个实例的注册,Autofac默认以最后一次注入的为准
// 请求ILogger实例容器返回ConsoleLogger实例
builder.RegisterType<ConsoleLogger>().As<ILogger>();
// 请求ILogger实例容器返回FileLogger实例
builder.RegisterType<FileLogger>().As<ILogger>();
// 最终请求ILogger实例容器返回的是FileLogger实例,Autofac以最后的为准
如果需要手动指定默认的,而不是使用最后一个,可以使用PreserveExistingDefaults()修饰
builder.RegisterType<ConsoleLogger>().As<ILogger>().PreserveExistingDefaults();
builder.RegisterType<FileLogger>().As<ILogger>();
// 最终请求ILogger实例容器返回的是ConsoleLogger实例,因为使用了PreserveExistingDefaults()修饰
条件注入
条件注入在Autofac4.4引入,使用4.4以后的版本可以使用
大多数情况下,如果一个类型注入了多个实现,使用PreserveExistingDefaults()手动指定就够了,但是有时候还不够,这时候可以使用条件注入
// 请求IService接口得到ServiceA
builder.RegisterType<ServiceA>()
.As<IService>();
// 请求IService接口得到ServiceA
builder.RegisterType<ServiceB>()
.As<IService>()
// 仅当IService没有注册过才会注册
.IfNotRegistered(typeof(IService));
// 最后请求IService获得的实例是ServiceA
builder.RegisterType<HandlerA>()
.AsSelf()
.As<IHandler>()
// 注册HandlerA在HandlerB之前,所以检查会认为没有注册
// 最后这条注册语句会成功执行
.IfNotRegistered(typeof(HandlerB));
builder.RegisterType<HandlerB>()
// 注册自己的类型,即HandlerB
.AsSelf()
.As<IHandler>();
builder.RegisterType<HandlerC>()
// 注册自己的类型,即HandlerC
.AsSelf()
.As<IHandler>()
// 不会执行,因为HandlerB已经注册了
.IfNotRegistered(typeof(HandlerB));
// 注册IManager
builder.RegisterType<Manager>()
.As<IManager>()
// 仅当IService和HandlerB都注册了对应服务时才会执行
.OnlyIf(reg =>
reg.IsRegistered(new TypedService(typeof(IService))) &&
reg.IsRegistered(new TypedService(typeof(HandlerB))));