07 | 用Autofac增强容器能力:引入面向切面编程(AOP)的能力
什么情况下需要引入第三方容器组件
- 基于名称的注入
- 我们需要把一个服务,按照名称来区分它不同的实现的时候
- 属性注入
- 之前提到过,我们的注入方式有
FromServices
的方式,还有构造函数入参的方式,直接可以把服务注册到某一个类的属性里面去。
- 之前提到过,我们的注入方式有
- 子容器
- 子容器其实可以理解成之前讲过的
Scope
,还可以用第三方的框架来实现一些特殊的子容器。
- 子容器其实可以理解成之前讲过的
- 基于动态代理的AOP
- 当我们需要在服务中注入我们额外的行为的时候,我们可以用动态代理的能力。
核心扩展点
public interface IServiceProviderFactory
- 第三方的依赖注入容器,都是使用了这个类来作为扩展点,把自己注入到我们整个的框架里面来,也就是说我们在使用这些依赖注入的框架的时候,我们不需要关注说,谁家的特性,谁家的接口时生命样子的,我们只需要使用官方核心的定义就可以了,我们不需要直接依赖这些框架。
示例
首先我们新建Web程序??选择API模板??新建Service文件夹??定义一个Service
using System;
namespace StarupDemo.Services
{
public interface IMyService
{
void ShowCode();
}
public class MyService : IMyService
{
public void ShowCode()
{
Console.WriteLine($"MyService.ShowCode : {GetHashCode()}");
}
}
public class MyService2 : IMyService
{
public MyNameService NameService { get; set; }
public void ShowCode()
{
Console.WriteLine($"MyService.ShowCode : {GetHashCode()} , NameService是不为空 : {NameService == null}");
}
}
public class MyNameService{ }
}
引入Autofac包(工具??NuGet包管理器??管理解决方案的NuGet程序包)
我们需要在Program
入口加一行代码
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
// 添加
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
});
我们需要在Startup
类添加一个方法ConfigureContrainer
,它的入参是AutoFac的ContainerBuilder
public void ConfigureContainer(ContainerBuilder builder)
{
// AutoFac注册方式
builder.RegisterType().As();
}
Autofac注册方式是和之前有所不同,先注册具体的实现,然后再告诉它我们想把它标记为哪个服务的类型,与我们之前的写法是相反的。
命名注入
当我们把需要把一个服务注册多次,并且用不同的命名来作为区分的时候,可以用这种方式。
builder.RegisterType
使用Named
,入参是一个服务名。
那如何使用它呢?
// 这里把根容器注入进来
public ILifetimeScope AutoFacContainer { get; private set; }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
AutoFacContainer = app.ApplicationServices.GetAutofacRoot();
var service = AutoFacContainer.ResolveNamed("service2");
service.ShowCode();
// ...
}
这里我们执行一下,打印一下内容。
那我们怎么获取没有命名的示例呢:
var servicename = AutoFacContainer.Resolve
servicename.ShowCode();
属性注入
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType();
builder.RegisterType().As().PropertiesAutowired();
}
注意,这里的NameService已经注册进去了,也就是说我们的属性注册已经成功了。
AOP(面向切面编程)
- AOP应用场景是指我们不期望改变原有类的情况下,在方法执行时嵌入一些逻辑,让我们可以在方法执行的切面上,任意的插入我们的逻辑
我们不对MyService
进行任何改动,仅仅添加了一个MyInterceptor
的实现。
IInterceptor
是AutoFac的面向切面的最重要的一个接口,它可以让我们把我们逻辑注入到方法的切面里面去。
第一行代码表示,我们可以在方法执行前执行逻辑。
**Proceed**
这个方法是指我么具体的方法的执行,如果说是这一句不执行的话,我们就相当于把我们切面的 方法拦截掉,让具体类的方法不执行。
第三行,就是方法执行后,也就是说我们可以在任意的方法执行后,插入我们的执行逻辑,并且决定原有的方法是否执行。
using Castle.DynamicProxy;
using System;
namespace StarupDemo.Services
{
public class MyInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"Intercept before , Method : {invocation.Method.Name}");
invocation.Proceed();
Console.WriteLine($"Intercept after , Method : {invocation.Method.Name}");
}
}
}
如何启用我们的切面呢?
首先,我们需要把我们的拦截器注册到容器里面去。
builder.RegisterType
然后我们把MyService2
注册进去
builder.RegisterType
**属性注入和上述一致**
开启拦截器需要我们使用**InterceptedBy()**
这个方法,并且把我们的**类型**
注册进去。最后还要**执行一个开关**
,让我们允许接口拦截器。
接口拦截器分两种类型,一个是接口类型,接口拦截器,一种是类拦截器。我们常用的就是接口拦截器,当我们的服务的类型是接口的时候,我们就需要使用这种方式。如果我们没有基于接口设计我们的类,而是去实现了类的话,我们就需要类拦截器。类拦截器就需要我们把方法设计为虚方法,这样子允许继承类重载的情况下,才可以拦截到我们具体的方法。
执行结果
可以看到拦截器方法执行前,和调用实现,之后的执行。
我们把执行注释掉//invocation.Proceed();
这就意味着我们不执行具体类的实现方法。
可以看到只输出了两条打印信息。
子容器
在之前讲到过,我们可以使用**Scope**
来创建子容器,在这里的场景的话,我们实际上是可以给子容器进行命名。Autofac具备给子容器进行命名的特性,在这里我们可以把一个服务注入到子容器当中,并且是特定命名的子容器,那就意味着,我们在其它子容器里面是获取不到这个对象的。
builder.RegisterType
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
AutoFacContainer = app.ApplicationServices.GetAutofacRoot();
using (var myScope = AutoFacContainer.BeginLifetimeScope("myScope"))
{
var service0 = myScope.Resolve();
using (var scope = AutoFacContainer.BeginLifetimeScope("myScope"))
{
var service1 = myScope.Resolve();
var service2 = myScope.Resolve();
Console.WriteLine($"service1 = service2 : {service1 == service2}");
Console.WriteLine($"service1 = service2 : {service1 == service2}");
}
}
}
这里我们可以看到,这里输出结果都是True
也就意味着在myScope
的子容器下面,不管我们再创建任何的子容器的生命周期,我们得到的都是同一个对象。
这样子有一个好处是但我们不期望,这个对象在根容器创建时,又希望它在某一定的范围内是单例模式的情况下,可以使用这种方式。