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``(``"service2"``);
使用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().As().**PropertiesAutowired**().**InterceptedBy**(**typeof(MyInterceptor)**).**EnableInterfaceInterceptors**();
**属性注入和上述一致**
开启拦截器需要我们使用**InterceptedBy()**这个方法,并且把我们的**类型**注册进去。最后还要**执行一个开关**,让我们允许接口拦截器。

接口拦截器分两种类型,一个是接口类型,接口拦截器,一种是类拦截器。我们常用的就是接口拦截器,当我们的服务的类型是接口的时候,我们就需要使用这种方式。如果我们没有基于接口设计我们的类,而是去实现了类的话,我们就需要类拦截器。类拦截器就需要我们把方法设计为虚方法,这样子允许继承类重载的情况下,才可以拦截到我们具体的方法。

执行结果

可以看到拦截器方法执行前,和调用实现,之后的执行。
我们把执行注释掉//invocation.Proceed(); 这就意味着我们不执行具体类的实现方法。

可以看到只输出了两条打印信息。

子容器
在之前讲到过,我们可以使用**Scope**来创建子容器,在这里的场景的话,我们实际上是可以给子容器进行命名。Autofac具备给子容器进行命名的特性,在这里我们可以把一个服务注入到子容器当中,并且是特定命名的子容器,那就意味着,我们在其它子容器里面是获取不到这个对象的。
builder.RegisterType().InstancePerMatchingLifetimeScope("myScope");

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的子容器下面,不管我们再创建任何的子容器的生命周期,我们得到的都是同一个对象。
这样子有一个好处是但我们不期望,这个对象在根容器创建时,又希望它在某一定的范围内是单例模式的情况下,可以使用这种方式。