第三方DI框架:Autofac
什么情况下需要引入第三方容器组件?
实际上是大部分情况默认的容器组件是够我们使用的,但是当我们需要一些非常特殊的场景时,如:
- 基于名称的注入:把一个服务按照名称来区分它不同的实现的时候
- 属性注入:我们的注入方式有FromService的方式、还有构造函数入参的方式,但是在开源社区我们会有很多这种属性注入的方式,也就是说我们可以直接把服务注册到某一个类的属性里面去而不需要定义构造函数
- 子容器:可以理解成之前讲过的Scope,但实际上我们还可以用第三方的框架来去实现些特殊的子容器
- 基于动态代理的AOP:当我们需要在服务中注入我们额外的行为的时候,我们可以用动态代理的能力
.NET Core的依赖注入框架的核心扩展点是:IServiceProviderFactory,即:
public interface IServiceProviderFactory<TContainerBuilder>
第三方的依赖注入容器都是使用了这个类来作为扩展点,把自己注入到我们整个的框架里面来,也就是说我们在使用这些依赖注入框架的时候,我们不需要关注说:谁家的特性、谁家的接口是什么样子的,我们只需要使用官方核心的定义就可以了,我们不需要直接依赖这些框架;
public interface IMyService{
void ShowCode();
}
public class MyService: IMyService{
public void ShowCode(){
Console.WriteLine($"MyService.ShowCode:{GetHashCode()}");
}
}
public class MyServiceV2: IMyService{
public MyNameService NameService{get; set;}
public void ShowCode(){
Console.WriteLine($"MyServiceV2.ShowCode:{GetHashCode()}, NameService是否为空:{NameService == null}");
}
}
public class MyNameService{}
引入Autofac包:
- Autofac.Extensions.DependencyInjection
- Autofac.Extras.DynamicProxy
在 入口文件 中加入:
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
的作用:注册第三方容器的入口
在 Startup.cs 中添加一个 ConfigureContainer方法:
服务注册进默认的容器之后,实际上会被 Autofac 接替,然后执行 ConfigureContainer
public void ConfigureContainer(ContainerBuilder builder){
// 一般的写法:先注册具体的实现,然后再告诉它我们想把它标记哪个服务的类型,与之前自带注入的写法是相反的
builder.RegisterType<MyService>().As<IMyService>();
// 命名注册:当我们需要把一个服务注册多次,并且用不同的命名来作为区分的时候,我们可以用这种方式
builder.RegisterType<MyServiceV2>().Named<IMyService>("service2");
}
上述代码中的 命名注册,是如何去使用它的呢?在 Starup.cs 中:
public ILifetimeScope AutofacContainer{get; private set;}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
// 这里是把跟容器注册进来
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
// Autofac容器获取实例的方式是一组Resolve的方法, ResolveNamed 和 ResolveKey 作用相同,用法不同而已;
var service = this.AutofacContainer.ResolveNamed<IMyService>("service2");
service.ShowCode(); // 这里输出的是 MyServiceV2
// 获取没有命名的服务,把 ResolveNamed 的 Named 去掉即可
var servicenamed = this.AutofacContainer.Resolve<IMyService>();
servicenamed.ShowCode(); // 这里输出的是 MyService
...
}
运行后输出:
注意:Autofac容器获取实例的方式是一组Resolve的方法:
属性注入
只需要在后面加入 .PropertiesAutowired() 即可:
public void ConfigureContainer(ContainerBuilder builder)
{
//// 一般的写法:先注册具体的实现,然后再告诉它我们想把它标记哪个服务的类型
//builder.RegisterType<MyService>().As<IMyService>();
//// 命名注册:当我们需要把一个服务注册多次,并且用不同的命名来作为区分的时候,我们可以用这种方式
//builder.RegisterType<MyServiceV2>().Named<IMyService>("service2");
builder.RegisterType<MyServiceV2>().As<IMyService>().PropertiesAutowired();
}
public ILifetimeScope AutofacContainer{get; private set;}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
// 这里是把根容器注册进来
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
// Autofac容器获取实例的方式是一组Resolve的方法
//var service = this.AutofacContainer.ResolveNamed<IMyService>("service2");
//service.ShowCode(); // 这里输出的是 MyServiceV2
// 获取没有命名的服务,把 ResolveNamed 的 Named 去掉即可
var servicenamed = this.AutofacContainer.Resolve<IMyService>();
servicenamed.ShowCode(); // 这里输出的是 MyService
...
}
NameService为空,说明 NameService 属性没有注入进来
我们需要将 MyNameService 也注入进容器中:
public void ConfigureContainer(ContainerBuilder builder)
{
//// 一般的写法:先注册具体的实现,然后再告诉它我们想把它标记哪个服务的类型
//builder.RegisterType<MyService>().As<IMyService>();
//// 命名注册:当我们需要把一个服务注册多次,并且用不同的命名来作为区分的时候,我们可以用这种方式
//builder.RegisterType<MyServiceV2>().Named<IMyService>("service2");
builder.RegisterType<MyServiceV2>().As<IMyService>().PropertiesAutowired();
builder.RegisterType<MyNameService>();
}
发现 MyNameService 已经不为空了,说明 NameService 已经有实例了,属性注入已经成功了
AOP
我们在不期望改变原有类的情况下,在方法执行时嵌入一些逻辑,让我们可以在方法执行的切面上任意的插入我们的逻辑
/// <summary>
/// IInterceptor 是 Autofac 的面向切面的最重要的一个接口,它可以把逻辑注入到方法的切面里面去
/// </summary>
public class MyInterceptor: IInterceptor // 类必须要继承至 IInterceptor,并且要实现 Intercept方法
{
public void Intercept(IInvocation invocation)
{
// 方法执行前
System.Console.WriteLine($"Intercept before,Method:{invocation.Method.Name}");
// 具体方法的执行,如果这句话不执行,相当于把切面的方法拦截掉,让具体类的方法不执行
invocation.Proceed();
// 方法执行后,也就是说可以在任意的方法执行后,插入执行逻辑,并且决定原有的方法是否执行
System.Console.WriteLine($"Intercept after,Method:{invocation.Method.Name}");
}
}
启动切面
public void ConfigureContainer(ContainerBuilder builder)
{
// 首先我们需要把我们的拦截器MyInterceptor注册到容器里面去
builder.RegisterType<MyInterceptor>();
// 然后我们把MyServiceV2注册进去,并且允许它属性注册(PropertiesAutowired),开启拦截器需要我们使用InterceptedBy这个方法,并且把我们的拦截器类型注册进去,最后还要执行一个开关让我们允许接口拦截器
// 拦截器分两种类型,一种是接口类型的接口拦截器(常用)和类拦截器,当我们的服务的类型是接口的时候就需要用接口拦截器类型;而如果我们没有基于接口设计我们的类,而是实现类了的话,我们就需要用类拦截器,类拦截器需要我们把里面的方法设计为虚方法,这样子允许继承类重载的情况下,才可以拦截到我们的具体方法
builder.RegisterType<MyServiceV2>().As<IMyService>().PropertiesAutowired().InterceptedBy(typeof(MyInterceptor)).EnableInterfaceInterceptors();
}
public ILifetimeScope AutofacContainer{get; private set;} // 这里是把根容器注册进来
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
var servicenamed = this.AutofacContainer.Resolve<IMyService>();
servicenamed.ShowCode(); // 这里输出的是 MyService
...
}
子容器
我们可以创建Scope来作为子容器;
下面代码中,我们实际上是可以给子容器进行命名,Autofac具备给子容器进行命名的特性;
#region 子容器
这里我们可以把一个服务注入到子容器中,并且是特定的命名的子容器,那就意味着其他的子容器里面是获取不到这个对象的;
builder.RegisterType<MyNameService>().InstancePerMatchingLifetimeScope("myscope");
#endregion
创建一个 myscope 的子容器
public ILifetimeScope AutofacContainer { get; private set; } // 这里是把跟容器注册进来
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
#region 创建一个 myscope 的子容器
using(var myscope = AutofacContainer.BeginLifetimeScope("myscope"))
{
var service0 = myscope.Resolve<MyNameService>();
using (var scope = myscope.BeginLifetimeScope())
{
var service1 = scope.Resolve<MyNameService>();
var service2 = scope.Resolve<MyNameService>();
Console.WriteLine($"service1=service2:{service1 == service2}");
Console.WriteLine($"service1=service0:{service1 == service0}");
}
}
#endregion
...
}
这意味着在 myscope 子容器下面,不管再创建任何子容器的生命周期,得到的都是同一个对象
这样子的好处是当不期望这个对象在根容器创建时,又希望它在某一定的范围内时单例模式的情况下,可以使用这种方式