什么情况下需要引入第三方容器组件

  • 基于名称的注入
    • 我们需要把一个服务,按照名称来区分它不同的实现的时候
  • 属性注入
    • 之前提到过,我们的注入方式有FromServices的方式,还有构造函数入参的方式,直接可以把服务注册到某一个类的属性里面去。
  • 子容器
    • 子容器其实可以理解成之前讲过的Scope,还可以用第三方的框架来实现一些特殊的子容器。
  • 基于动态代理的AOP
    • 当我们需要在服务中注入我们额外的行为的时候,我们可以用动态代理的能力。

核心扩展点

  • public interface IServiceProviderFactory<TContainerBuilder>
  • 第三方的依赖注入容器,都是使用了这个类来作为扩展点,把自己注入到我们整个的框架里面来,也就是说我们在使用这些依赖注入的框架的时候,我们不需要关注说,谁家的特性,谁家的接口时生命样子的,我们只需要使用官方核心的定义就可以了,我们不需要直接依赖这些框架。

示例

首先我们新建Web程序👉选择API模板👉新建Service文件夹👉定义一个Service

  1. using System;
  2. namespace StarupDemo.Services
  3. {
  4. public interface IMyService
  5. {
  6. void ShowCode();
  7. }
  8. public class MyService : IMyService
  9. {
  10. public void ShowCode()
  11. {
  12. Console.WriteLine($"MyService.ShowCode : {GetHashCode()}");
  13. }
  14. }
  15. public class MyService2 : IMyService
  16. {
  17. public MyNameService NameService { get; set; }
  18. public void ShowCode()
  19. {
  20. Console.WriteLine($"MyService.ShowCode : {GetHashCode()} , NameService是不为空 : {NameService == null}");
  21. }
  22. }
  23. public class MyNameService{ }
  24. }

引入Autofac包(工具👉NuGet包管理器👉管理解决方案的NuGet程序包)
image.png
我们需要在Program入口加一行代码

  1. public static IHostBuilder CreateHostBuilder(string[] args) =>
  2. Host.CreateDefaultBuilder(args)
  3. // 添加
  4. .UseServiceProviderFactory(new AutofacServiceProviderFactory())
  5. .ConfigureWebHostDefaults(webBuilder =>
  6. {
  7. webBuilder.UseStartup<Startup>();
  8. });

我们需要在Startup类添加一个方法ConfigureContrainer,它的入参是AutoFac的ContainerBuilder

  1. public void ConfigureContainer(ContainerBuilder builder)
  2. {
  3. // AutoFac注册方式
  4. builder.RegisterType<MyService>().As<IMyService>();
  5. }

Autofac注册方式是和之前有所不同,先注册具体的实现,然后再告诉它我们想把它标记为哪个服务的类型,与我们之前的写法是相反的。

命名注入
当我们把需要把一个服务注册多次,并且用不同的命名来作为区分的时候,可以用这种方式。
builder.RegisterType<MyService2>().``Named``<IMyService>(``"service2"``);
使用Named,入参是一个服务名。
那如何使用它呢?

  1. // 这里把根容器注入进来
  2. public ILifetimeScope AutoFacContainer { get; private set; }
  3. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  4. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  5. {
  6. AutoFacContainer = app.ApplicationServices.GetAutofacRoot();
  7. var service = AutoFacContainer.ResolveNamed<IMyService>("service2");
  8. service.ShowCode();
  9. // ...
  10. }

这里我们执行一下,打印一下内容。
image.png
那我们怎么获取没有命名的示例呢:
var servicename = AutoFacContainer.Resolve<IMyService>();
servicename.ShowCode();

属性注入

  1. public void ConfigureContainer(ContainerBuilder builder)
  2. {
  3. builder.RegisterType<MyNameService>();
  4. builder.RegisterType<MyService2>().As<IMyService>().PropertiesAutowired();
  5. }

image.png
注意,这里的NameService已经注册进去了,也就是说我们的属性注册已经成功了。

AOP(面向切面编程)

  • AOP应用场景是指我们不期望改变原有类的情况下,在方法执行时嵌入一些逻辑,让我们可以在方法执行的切面上,任意的插入我们的逻辑

我们不对MyService进行任何改动,仅仅添加了一个MyInterceptor的实现。
IInterceptor是AutoFac的面向切面的最重要的一个接口,它可以让我们把我们逻辑注入到方法的切面里面去。
第一行代码表示,我们可以在方法执行前执行逻辑。
**Proceed**这个方法是指我么具体的方法的执行,如果说是这一句不执行的话,我们就相当于把我们切面的 方法拦截掉,让具体类的方法不执行。
第三行,就是方法执行后,也就是说我们可以在任意的方法执行后,插入我们的执行逻辑,并且决定原有的方法是否执行。

  1. using Castle.DynamicProxy;
  2. using System;
  3. namespace StarupDemo.Services
  4. {
  5. public class MyInterceptor : IInterceptor
  6. {
  7. public void Intercept(IInvocation invocation)
  8. {
  9. Console.WriteLine($"Intercept before , Method : {invocation.Method.Name}");
  10. invocation.Proceed();
  11. Console.WriteLine($"Intercept after , Method : {invocation.Method.Name}");
  12. }
  13. }
  14. }

如何启用我们的切面呢?
首先,我们需要把我们的拦截器注册到容器里面去。
builder.RegisterType<MyInterceptor>();
然后我们把MyService2注册进去
builder.RegisterType<MyService2>().As<IMyService>().**PropertiesAutowired**().**InterceptedBy**(**typeof(MyInterceptor)**).**EnableInterfaceInterceptors**();
**属性注入和上述一致**
开启拦截器需要我们使用**InterceptedBy()**这个方法,并且把我们的**类型**注册进去。最后还要**执行一个开关**,让我们允许接口拦截器。

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

执行结果
image.png
可以看到拦截器方法执行前,和调用实现,之后的执行。
我们把执行注释掉//invocation.Proceed(); 这就意味着我们不执行具体类的实现方法。
image.png
可以看到只输出了两条打印信息。

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

  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  2. {
  3. AutoFacContainer = app.ApplicationServices.GetAutofacRoot();
  4. using (var myScope = AutoFacContainer.BeginLifetimeScope("myScope"))
  5. {
  6. var service0 = myScope.Resolve<MyNameService>();
  7. using (var scope = AutoFacContainer.BeginLifetimeScope("myScope"))
  8. {
  9. var service1 = myScope.Resolve<MyNameService>();
  10. var service2 = myScope.Resolve<MyNameService>();
  11. Console.WriteLine($"service1 = service2 : {service1 == service2}");
  12. Console.WriteLine($"service1 = service2 : {service1 == service2}");
  13. }
  14. }
  15. }

这里我们可以看到,这里输出结果都是True
image.png
也就意味着在myScope的子容器下面,不管我们再创建任何的子容器的生命周期,我们得到的都是同一个对象。
这样子有一个好处是但我们不期望,这个对象在根容器创建时,又希望它在某一定的范围内是单例模式的情况下,可以使用这种方式。