依赖注入框架 DI/IoC

作用:管理类与类之间的依赖关系,帮我们构造类、确保我们代码的可维护性和可扩展性
ASP.Net Core 中,主要是用来管理我们对象的依赖、生命周期,负责各个组件之间的协作

ASP.NET 自带的依赖注入框架组件包:内置

  • Microsoft.Extensions.DependencyInjection.Abstractions
  • Microsoft.Extensions.DependencyInjection

这两个:一个是抽象包、一个是具体的实现,它们用到了一个设计模式:接口实现分离模式,即:我们的组件只需要依赖抽象它的抽象接口,而不需要它的实现;当使用的时候注入它的具体实现即可;
好处是:可以在使用时决定我们用具体哪个实现,未来我们可以去做任意的扩展替换具体的依赖注入框架的具体实现;

依赖注入框架的核心类型有:

  • IServiceCollection:负责服务的注册
  • ServiceDescriptor:每一个服务注册时的信息
  • IServiceProvider:具体的容器,由 ServiceCollection Build出来的
  • IServiceScope:表示一个容器的子容器的生命周期

依赖注入的生命周期

  • 单例 Singleton:只有一个对象,无论获取多少次
  • 作用域 Scoped:在Scope的生存周期内(也就是容器的生存周期内、活着子容器的生存周期内),得到的一个单例模式,而如果容器释放掉,那么对应的对象也会被释放掉;
  • 瞬时(暂时) Tranient :每次从容器中获取的对象都是全新的对象
public interface IMySingletonService { }
public class MySingletonService: IMySingletonService
{}

public interface IMyScopeService { }
public class MyScopeService : IMyScopeService
{}

public interface IMyTransientService { }
public class MyTransientService : IMyTransientService
{}

将上面注入到容器中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IMySingletonService, MySingletonService>();
    services.AddScoped<IMyScopeService, MyScopeService>();
    services.AddTransient<IMyTransientService, MyTransientService>();

    Console.WriteLine("Startup.ConfigureServices");
    services.AddControllers();
}

在控制器中的Action中使用:

[HttpGet]
public int GetService([FromServices]IMySingletonService singleton1,        // FromServices 的作用就是从容器中获取对象
                      [FromServices]IMySingletonService singleton2,
                      [FromServices]IMyScopeService scope1,
                      [FromServices]IMyScopeService scope2,
                      [FromServices]IMyTransientService transient1,
                      [FromServices]IMyTransientService transient2,)
{
    // 输出对象的hash指(如果两个对象的hash值相同,那么就可以说这两个对象就是同一个对象)
    Console.WriteLine($"singleton1:{singleton1.GetHashCode()}");
    Console.WriteLine($"singleton2:{singleton2.GetHashCode()}");

    Console.WriteLine($"transient1:{transient1.GetHashCode()}");
    Console.WriteLine($"transient2:{transient2.GetHashCode()}");

    Console.WriteLine($"scope1:{scope1.GetHashCode()}");
    Console.WriteLine($"scope2:{scope2.GetHashCode()}");
}

运行,再刷新一次,对比两次输出结果;
image

单例模式两次的 HashCode 没有变化

两个瞬时服务两次的 HashCode 完全不同,意味着瞬时服务每次请求都会得到一个新对象

范围服务每个请求内是相同的,不同的请求之间得到的对象实例是不同的

服务注册方式

1、除了用上面提到的 泛型方式注入我们的服务 外,如下:
services.AddSingleton<IMySingletonService, MySingletonService>();

2、将实例直接注入到容器中:
services.AddSingleton<IMySingletonService>(new MySingletonService());

3、工厂的方式注册服务:

services.AddSingleton<IMySingletonService>(serviceProvider => {
    // 因为这里我们可以用 serviceProvider,所以我们通过这个从容器里面获取多个对象,然后进行组装,得到我们最终的实现实例
    // 即用工厂的方式可以设计得比较复杂,如:实现类依赖了我们容器里的另外一个类的情况,或者我们期望用另一个类来包装我原有的实现的时候就可以使用此方法
    return new MySingletonService();
})

尝试注册服务(即:服务如果注册过了,就不会再注册了)

分两种情况:1、服务的接口类型重复 2、服务的实现类重复

TryAddSingleton
服务的接口类型如果已被注册过了,那么就无法再注册了;

services.AddSingleton<IMySingletonService, MySingletonService>();
services.TryAddSingleton<IMySingletonService, MySingletonServiceEx>();    // 尝试注册,这里无法注册,因为使用 TryAddSingleton ,即:服务的接口类型已被注册
// 注意,这里看的是 接口 是否重复注册,而不是看实现类是否重复

// 在控制器中:
public int GetServiceList([FromServices]IEnumerable<IMySingletonService> services){   // 获取注册过的所有IMySingletonService的对象
    foreach(var item in services){
        Console.WriteLine($"获取到服务实例:{item.ToString()}:{item.GetHashCode()}");
    }
    return 1;
}

发现只有一个:
image

TryAddEnumerable
服务的接口类型即使相同,而实现类不同,那么这个服务同样是能被注册的

services.AddSingleton<IMySingletonService, MySingletonService>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMySingletonService, MySingletonService>());    // 尝试注册,可以注册,使用 TryAddEnumerable 后,即使服务的接口类型相同,但是其实现类不同,也能被注册;
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMySingletonService, MySingletonServiceEx>());   // 无法注册
// 注意,这里看的是 实现类 是否重复注册,而不是看服务的接口类型是否重复

移除和替换 注册

services.RemoveAll<IMySingletonService>();

services.Replace(ServiceDescriptor.Singleton<IMySingletonService, MySingletonServiceEx>());     // 将实现类替换成 MySingletonServiceEx

泛型模板 注册
当我们需要注册一组泛型实现的时候,我们实际上在注册的时候是不知道我们的泛型类的具体的类型入参是什么的,依赖注入框架为我们提供了泛型模板的注册方式,就意味着我们可以把泛型模板注册进去,通过一行代码来注册所有的泛型的具体的实现;

public interface IGenericService<T>{}

public class GenericService<T>: IGenericService<T>{
    publiic Data {get; private set;}
    public GenericService(T data){
        this.Data = data;
    }
}

// 注册泛型模板:
services.AddSingleton(typeof(IGenericService<>), typeof(GenericService<>));
// 它的生命周期与之前的注册方式是一致的,不过它无法通过泛型 API 注册,需要注册两个 service 的 type:
// - 第一个入参是服务的类型
// - 第二个入参是服务实现的类型


// 控制器中的使用
// 在构造函数中添加两个入参,IMySingletonService 和 IGenericService
// 通过断点调试查看 genericService 的类型可得知,泛型的具体实现可以用容器里面的任意类型来替代
public WeatherForecastController(ILogger<WeatherForecastController> logger, IMySingletonService mySingletonService, IGenericService<IMySingletonService> genericService)
{
    _orderService = orderService;
    _logger = logger;
}

在 controller 中有两种依赖注入的实例的获取方式:

  • 通过 controller 构造函数注入
  • 通过 [FromServices] 注入

当定义一个 controller 的时候,它的服务是大部分接口都需要使用的情况下,推荐的做法是用构造函数注入的方式,如果这个服务仅仅在某一个接口使用的情况下,推荐使用 [FromServices] 注入;

posted @ 2022-04-30 23:14  醉马踏千秋  阅读(164)  评论(0编辑  收藏  举报