1. Ioc 与 DI

Ioc 和DI 这两个词大家都应该比较熟悉,这两者已经在各种开发语言各种框架中普遍使用,成为框架中的一种基本设施了。

Ioc 是控制反转, Inversion of Control 的缩写,DI 是依赖注入,Inject Dependency 的缩写。

所谓控制反转,反转的是类与类之间依赖的创建。类型A依赖于类型B时,不依赖于具体的类型,而是依赖于抽象,不在类A中直接 new 类B的对象,而是通过外部传入依赖的类型对象,以实现类与类之间的解耦。所谓依赖注入,就是由一个外部容器对各种依赖类型对象进行管理,包括对象的创建、销毁、状态保持等,并在某一个类型需要使用其他类型对象的时候,由容器进行传入。

下图是一张网图,是关于Ioc解耦比较经典的图示过程了。至于依赖解耦的好处,就不在这里细讲了,如果有对依赖注入基本概念不理解的,可以稍微搜索一下相关的文章,也可以参考 ASP.NET Core 依赖注入 | Microsoft Learn 官方文档中的讲解。

Ioc是一种设计思想,而DI是这种思想的具体实现。依赖注入是一种设计模式,是对面向对象编程五大基本原则中的依赖倒置原则的实践,其中很重要的一个点就是 Ioc 容器的实现。

2. .NET Core 依赖注入的基本用法

在 .NET Core 平台下,有一套自带的轻量级Ioc框架,如果是ASP.NET Core项目,更是在使用主机的时候自动集成了进去,我们在startup类中的ConfigureServices方法中的代码就是往容器中配置依赖注入关系,如果是控制台项目的话,还需要自己去集成。除此之外,.NET 平台下还有一些第三方依赖注入框架,如 Autofac、Unity、Castle Windsor等。

这里先不讨论第三方框架的内容,先简单介绍一下.Net Core平台自带的Ioc框架的使用。

2.1 服务

依赖项注入术语中,服务通常是指向其他对象提供服务的对象,既可以作为其他类的依赖项,也可能依赖于其他服务。服务是Ioc容器管理的对象。

2.2 服务生命周期

使用了依赖注入框架之后,所有我们注入到容器中的类型的创建、销毁工作都由容器来完成,那么容器什么时候创建一个类型实例,什么时候销毁一个类型实例呢?这就涉及到注入服务的生命周期了。根据我们的需要,我们可以向容器中注册服务的时候,对服务的生命周期进行设置。服务的生命周期有以下三种:

(1) 单例 Singleton

注册成单例模式的服务,整个应用程序生命周期以内只创建一个实例。在应用内第一个使用到该服务时创建,在应用程序停止时销毁。

在某些情况下,对于某些特殊的类,我们需要注册成单例模式,这可以减少实例初始化的消耗,还能实现跨 Service 事务的功能。

(2)范围(或者作用域) Scoped

在同一个范围内只初始化一个实例 。在 web 应用中,可以理解为每一个 request 级别只创建一个实例,同一个 http request 会在一个 scope 内。

(3)多例 Tranisent

每一次使用到服务时都会创建一个新的实例,每一次对该依赖的获取都是一个新实例。

2.3 服务注册

在ASP.NET Core这样的web应用框架中,在使用主机的时候就自动集成了依赖注入框架,之后我们可以通过 IServiceCollection 对象来注册依赖注入关系。前面入口文件一篇讲过,.NET 6 之前可以在 Startup 类中的 ConfigureServices 方法中进行注册,该方法传入IServiceCollection参数,.NET 6 之后,可以通过 WebApplicationBuilder 对象的 Services属性进行注册。

服务注册常用的方法如下:

  • Add 方法

    通过参数 ServiceDescriptor 将服务类型、实现类型、生命周期等信息传入进去,是服务注册最基本的方法。其中 ServiceDescriptor 参数又有多种变形。

    // 最基本的服务注册方法,除此之外还有其他各种变形
    builder.Services.Add(new ServiceDescriptor(typeof(IRabbit), typeof(Rabbit), ServiceLifetime.Transient));
    builder.Services.Add(ServiceDescriptor.Scoped<IRabbit, Rabbit>());
    builder.Services.Add(ServiceDescriptor.Singleton(typeof(IRabbit), (services) => new Rabbit()));
  • Add{lifetime}扩展方法

    基于 Add 方法的扩展方法,包括以下几种,每种都有多个重载:

    // 基于生命周期的扩展方法,以下为实例,正式开发中不可能将一个类型注册为多个生命周期,会抛出异常
    builder.Services.AddTransient<IRabbit, Rabbit>();
    builder.Services.AddTransient(typeof(IRabbit), typeof(Rabbit));
    builder.Services.AddScoped<IRabbit, Rabbit>();
    builder.Services.AddSingleton<IRabbit, Rabbit>();
  • TryAdd{lifetime}扩展方法

    对于 Add{lifetime} 方法的扩展,位于命名空间 Microsoft.Extensions.DependencyInjection.Extensions 下。

    与 Add{lifetime} 方法相比,差别在于当使用 Add{lifetime} 方法将同样的服务注册了多次时,在使用 IEnumerable<{Service}> 解析服务时,就会产生多个实例的副本,这可能会导致一些意料之外的 bug,特别是单例生命周期的服务。

    // 同一个服务同一个实现注入多次
    builder.Services.AddSingleton<IRabbit, Rabbit>();
    builder.Services.AddSingleton<IRabbit, Rabbit>(); [ApiController]
    [Route("[controller]")]
    public class InjectTestController : ControllerBase
    {
    private readonly IEnumerable<IRabbit> _rabbits;
    public InjectTestController(IEnumerable<IRabbit> rabbits)
    {
    _rabbits = rabbits; [HttpGet("")]
    public Task InjectTest()
    {
    // 2个IRabbit实例
    Console.WriteLine(_rabbits.Count());
    var rabbit1 = _rabbits.First();
    var rabbit2 = _rabbits.ElementAt(1);
    // 都是 Rabbit 类型
    Console.WriteLine(rabbit1 is Rabbit);
    Console.WriteLine(rabbit2 is Rabbit);
    // 两个实例不是同一个
    Console.WriteLine(rabbit1 == rabbit2);
    return Task.CompletedTask;
    }
    }

    调用接口后,打印输出结果如下:

    而使用 TryAdd{lifetime} 方法,当DI容器中已存在指定类型的服务时,则不进行任何操作;反之,则将该服务注入到DI容器中。

    将服务注册改成以下代码:

    builder.Services.AddTransient<IRabbit, Rabbit>();
    // 由于上面已经注册了服务类型 IRabbit,所以下面的代码不不会执行任何操作(与生命周期无关)
    builder.Services.TryAddTransient<IRabbit, Rabbit>();
    builder.Services.TryAddTransient<IRabbit, Rabbit1>();

    在上面的控制器中新增以下方法:

    [HttpGet(nameof(InjectTest1))]
    public Task InjectTest1()
    {
    // 只有1个IRabbit实例
    Console.WriteLine(_rabbits.Count());
    var rabbit1 = _rabbits.First();
    // 都是 Rabbit 类型
    Console.WriteLine(rabbit1 is Rabbit);
    return Task.CompletedTask;
    }

    调用接口后,打印输出结果如下:

  • TryAddEnumerable 方法

    与 TryAdd 对应,区别在于TryAdd仅根据服务类型来判断是否要进行注册,而TryAddEnumerable则是根据服务类型和实现类型一同进行判断是否要进行注册,常常用于注册同一服务类型的多个不同实现。

    将服务注册改成以下代码:

    builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IRabbit, Rabbit>());
    builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
    // 未进行任何操作,因为 IRabbit 服务的 Rabbit实现在上面已经注册了
    builder.Services.TryAddEnumerable(ServiceDescriptor.Transient<IRabbit, Rabbit>());

    在上面的控制器新增一个方法:

    [HttpGet(nameof(InjectTest2))]
    public Task InjectTest2()
    {
    // 2个IRabbit实例
    Console.WriteLine(_rabbits.Count());
    var rabbit1 = _rabbits.First();
    var rabbit2 = _rabbits.ElementAt(1);
    // 第一个是 Rabbit 类型,第二个是 Rabbit1类型
    Console.WriteLine(rabbit1 is Rabbit);
    Console.WriteLine(rabbit2 is Rabbit1);
    return Task.CompletedTask;
    }

    调用接口后,控制台打印如下:

  • Repalce 与 Remove 方法

    当我们想要从Ioc容器中替换或是移除某些已经注册的服务时,可以使用Replace和Remove。

    // 将容器中注册的IRabbit实现替换为 Rabbit1
    builder.Services.Replace(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
    // 从容器中 IRabbit 注册的实现 Rabbit1
    builder.Services.Remove(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
    // 移除 IRabbit服务的所有注册
    builder.Services.RemoveAll<IRabbit>();
    // 清空容器中的所有服务注册
    builder.Services.Clear();

以上是 .NET Core 框架自带的 Ioc 容器的一些基本概念和依赖关系注入的介绍,下一章是注入到容器中的服务使用部分。

参考文章:

ASP.NET Core 依赖注入 | Microsoft Learn

理解ASP.NET Core - 依赖注入(Dependency Injection)

ASP.NET Core 系列:

目录:ASP.NET Core 系列总结

上一篇:ASP.NET Core - 自定义中间件

ASP.NET Core - 依赖注入(一)的更多相关文章

  1. # ASP.NET Core依赖注入解读&使用Autofac替代实现

    标签: 依赖注入 Autofac ASPNETCore ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Aut ...

  2. 实现BUG自动检测 - ASP.NET Core依赖注入

    我个人比较懒,能自动做的事绝不手动做,最近在用ASP.NET Core写一个项目,过程中会积累一些方便的工具类或框架,分享出来欢迎大家点评. 如果以后有时间的话,我打算写一个系列的[实现BUG自动检测 ...

  3. [译]ASP.NET Core依赖注入深入讨论

    原文链接:ASP.NET Core Dependency Injection Deep Dive - Joonas W's blog 这篇文章我们来深入探讨ASP.NET Core.MVC Core中 ...

  4. asp.net core 依赖注入几种常见情况

    先读一篇注入入门 全面理解 ASP.NET Core 依赖注入, 学习一下基本使用 然后学习一招, 不使用接口规范, 直接写功能类, 一般情况下可以用来做单例. 参考https://www.cnblo ...

  5. ASP.NET Core依赖注入——依赖注入最佳实践

    在这篇文章中,我们将深入研究.NET Core和ASP.NET Core MVC中的依赖注入,将介绍几乎所有可能的选项,依赖注入是ASP.Net Core的核心,我将分享在ASP.Net Core应用 ...

  6. 自动化CodeReview - ASP.NET Core依赖注入

    自动化CodeReview系列目录 自动化CodeReview - ASP.NET Core依赖注入 自动化CodeReview - ASP.NET Core请求参数验证 我个人比较懒,能自动做的事绝 ...

  7. ASP.NET Core 依赖注入最佳实践——提示与技巧

    在这篇文章,我将分享一些在ASP.NET Core程序中使用依赖注入的个人经验和建议.这些原则背后的动机如下: 高效地设计服务和它们的依赖. 预防多线程问题. 预防内存泄漏. 预防潜在的BUG. 这篇 ...

  8. ASP.NET Core依赖注入最佳实践,提示&技巧

    分享翻译一篇Abp框架作者(Halil İbrahim Kalkan)关于ASP.NET Core依赖注入的博文. 在本文中,我将分享我在ASP.NET Core应用程序中使用依赖注入的经验和建议. ...

  9. ASP.NET Core依赖注入解读&使用Autofac替代实现【转载】

    ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Autofac实现和自定义实现扩展方法 3.1 安装Autof ...

  10. ASP.NET Core 依赖注入基本用法

    ASP.NET Core 依赖注入 ASP.NET Core从框架层对依赖注入提供支持.也就是说,如果你不了解依赖注入,将很难适应 ASP.NET Core的开发模式.本文将介绍依赖注入的基本概念,并 ...

随机推荐

  1. Easy-Classification-分类框架设计

    1. 框架介绍 Easy-Classification是一个应用于分类任务的深度学习框架,它集成了众多成熟的分类神经网络模型,可帮助使用者简单快速的构建分类训练任务. 框架源代码:https://gi ...

  2. ElasticSearch7.6.1学习笔记-狂神

    ElasticSearch:7.6.1 https://gitee.com/yujie.louis/elastic-search 笔记,代码,安装包等 什么是ElasticSearch? Elasti ...

  3. easui datagrid 行获取后台sql所有数据:支持行chockbox多选,输出选中行任意属性;支持点击表中属性实现跳转;支持分页。

    easyUI datagrid 代码: <table id="tabgrid20170726191838251403" class="easyui-datagrid ...

  4. 教你用JavaScript实现随机点名

    案例介绍 欢迎来到我的小院,我是霍大侠,恭喜你今天又要进步一点点了!我们来用JavaScript相关知识,做一个随机点名的案例.你可以通过点击开始按钮控制上方名字的闪动,点击停止按钮可以随机选定一个名 ...

  5. 【大数据面试】Flink 04:状态编程与容错机制、Table API、SQL、Flink CEP

    六.状态编程与容错机制 1.状态介绍 (1)分类 流式计算分为无状态和有状态 无状态流针对每个独立事件输出结果,有状态流需要维护一个状态,并基于多个事件输出结果(当前事件+当前状态值) (2)有状态计 ...

  6. 解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

    大家好,又见面了. 本文是笔者作为掘金技术社区签约作者的身份输出的缓存专栏系列内容,将会通过系列专题,讲清楚缓存的方方面面.如果感兴趣,欢迎关注以获取后续更新. 上一篇文章中,我们继Guava Cac ...

  7. Wireshark网卡无法找到或没有显示的问题

    问题背景 最近在处理公司内网域名解析的问题,发现配置好一个新域名在内网环境可以正常解析成内网IP,但使用深信服VPN却无法正常解析,并且其他域名使用深信服VPN可以正常解析,所以参考<内网域名解 ...

  8. [机器学习] t-SNE聚类算法实践指南

    ​  转载于比PCA降维更高级--(R/Python)t-SNE聚类算法实践指南-阿里云开发者社区 作者介绍:Saurabh.jaju2 Saurabh是一名数据科学家和软件工程师,熟练分析各种数据集 ...

  9. [深度学习] fast-reid入门教程

    fast-reid入门教程 ReID,全拼为Re-identification,目的是利用各种智能算法在图像数据库中找到与要搜索的目标相似的对象.ReID是图像检索的一个子任务,本质上是图像检索而不是 ...

  10. 已完成 10000 多次提交,Solon Java Framework v1.12.1 发布

    一个更现代感的 Java 应用开发框架:更快.更小.更自由.没有 Spring,没有 Servlet,没有 JavaEE:独立的轻量生态.主框架仅 0.1 MB. @Controller public ...