最通俗易懂的依赖注入之服务注册与注入

推荐关注「码侠江湖」星标,时刻不忘江湖事

这篇文章是 ASP.NET 6 依赖注入系列文章的第 4 篇,点击上方蓝字可以阅读整个系列。

在上一篇文章中,我们讨论了依赖注入的服务容器与服务作用域。

接下来,在这篇文章中,我们继续深入了解服务注册与注入相关的内容。

1d41557e071571aeca72f603bb93344c.png

服务注册

现在,让我们回头看一看ServiceCollection服务集合类型。

我们现在已经知道,根容器是通过调用服务集合的BuildSerivceProvider扩展方法创建的。

服务集合ServiceCollection对象是一个存放服务注册信息的集合。

以下是它的部分源码,完整代码在这里。

public class ServiceCollection : IServiceCollection
{private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();private bool _isReadOnly;public int Count => _descriptors.Count;public ServiceDescriptor this[int index]{get{return _descriptors[index];}set{CheckReadOnly();_descriptors[index] = value;}}// ...
}

通过这部分代码可以发现,ServiceCollection本质上是一个元素类型为ServiceDescriptor的集合。

ServiceDescriptor 是服务描述类,它描述的是某个注册服务的基本信息,其中包含了服务类型本身以及它的实现类型,还有生命周期模式。

服务注册的本质就是创建相应的服务描述对象,并将其添加到服务集合对象中的过程。

依赖注入系统就是利用服务描述信息,才能够提供我们所依赖的服务实例,并注入其中。

依赖注入系统就会根据指定的服务类型从服务集合中,找到相应的服务描述对象,并且根据它提供的类型信息,来创建服务实例。

构造函数注入

依赖注入系统在创建服务实例时会先调用它的构造函数。

如果构造函数有参数,那么传入构造函数的所有参数必须也必须先进行初始化。

所以,服务实现类的构造函数必须具备一个基本的条件,那就是服务容器能够提供构造函数所需的所有参数,这就是构造函数注入。

如果当我们的服务实现类有多个重载版本的构造函数时,那么依赖注入系统会如何进行选择呢?

比如下面这个示例:

public static class Sample
{public interface IAccount{ }public interface IMessage{ }public interface ITool{ }public interface ITest{ }public class Account: IAccount{}public class Message: IMessage{}public class Tool: ITool{}public class Test: ITest{public Test(IAccount account){Console.WriteLine($"Ctor:Test(IAccount)");}public Test(IAccount account, IMessage message){Console.WriteLine($"Ctor:Test(IAccount,IMessage)");}public Test(IAccount account, IMessage message, ITool tool){Console.WriteLine($"Ctor:Test(IAccount,IMessage,ITool)");}}public static void Main(){var test = new ServiceCollection().AddTransient<IAccount, Account>().AddTransient<IMessage, Message>().AddTransient<ITest, Test>().BuildServiceProvider().GetService<ITest>();}
}

在这个例子中,定义了 4 个服务接口IAccount IMessage ITool ITest以及它们的实现类。

其中Test类型中定义了 3 个构造函数,它们的每一个参数都是一个服务。

为了确定依赖注入系统,会选择哪个构造函数来创建服务实例,每个构造函数都会在控制台中输出自身的字符串标识。

Main 方法中创建了一个服务集合对象,并且注册了除ITool以外的其它 3 个服务接口。

那么当我们获取ITest的服务实例时,会发生什么情况呢?它会通过执行哪个构造函数创建实例呢?

我们可以尝试分析一下,对于定义在Test类中的 3 个构造函数来说,

由于我们只注册了IAccountIMessage服务接口,所以服务容器只能够提供给前两个构造函数的所有参数,

而第三个构造函数只有一个ITool类型的参数,服务容器无法提供。

根据前面所说的基本条件「服务容器能够提供构造函数所需的所有参数」,此种情况下,也只有Test类型的前两个构造函数是符合条件的。

那么在所有符合条件的构造函数中,依赖注入系统又会如何选择呢?

这里用一个专业的说法来描述:「如果某个构造函数的参数类型集合,能够成为所有合法构造函数参数类型集合的超集,那么这个构造函数就会被依赖注入系统选择。」

简单来说,就是在所有符合条件的构造函数中,选择参数最多的那个。

如果按照这两个条件来分析的话,那么应该是第二个构造函数,以下执行结果证明了这一点。

9072a26ab5770a94eb0e2af874953797.png

接下来,我们对上面的示例改动一下,修改了Test类型的构造函数:

public class Test: ITest
{public Test(IAccount account, IMessage message){Console.WriteLine($"Ctor:Test(IAccount)");}public Test(IMessage message, ITool tool){Console.WriteLine($"Ctor:Test(IAccount,IMessage)");}
}public static void Main()
{var test = new ServiceCollection().AddTransient<IAccount, Account>().AddTransient<IMessage, Message>().AddTransient<ITool, Tool>().AddTransient<ITest, Test>().BuildServiceProvider().GetService<ITest>();
}

我们只为Test类型定义了两个构造函数,它们都具有两个参数:

  • 一个构造函数的参数是 IAccount 和 IMessage 接口;

  • 另一个构造函数的参数是 IMessage 和 ITool 接口。

并且在 Main 方法中,将每个服务接口都进行了注册。

那么此种情况下,我们是否能成功获取一个ITest对象呢?如果可以的话,它又是通过执行哪个构造函数创建的呢?

虽然Test的两个构造函数的参数都可以由服务容器提供,并且也满足了第一个条件。

但是还有一个条件没有达成,那就是:没有一个构造函数的参数类型集合,能够成为所有合法构造函数类型集合的超集。

也就说,无法满足第二个条件,因此依赖注入系统无法就选择无能了。

运行这个示例,抛出如下的异常提示:无法从两个候选的构造函数中选择一个最优的来创建服务实例。

35a5096eb0a0ff65a92790286eea5b0e.png

注入方式

实际上,在依赖注入的世界中,除了构造函数注入以外,注入方式还有很多种:如属性注入、方法注入、特性注入等。

「属性注入」,就是服务类把所依赖的其它服务,以属性方式声明,并以属性方式注入进来。

依赖注入系统在实例化服务类时,会通过属性把服务类所需要的对象注入进来。

「方法注入」,就是当服务类中的某个方法的参数依赖其它服务时,以参数形式注入进来。

「特性注入」,可以说是其它注入方式的一种补充。

比如有多个构造函数都符合注入的标准条件,但你只想让其中一个构造函数拥有被注入的能力,这时候可以通过特性来修饰这个构造函数,指定它是唯一可注入的构造函数。

目前,.NET 6 的依赖注入系统原生只支持构造函数注入,不过在某些类型的 Web 应用扩展下,也支持特定的方法注入、特性注入、甚至属性注入。

更多精彩内容,请关注我▼▼

5f9c062fea7608622018437840658441.gif

如果喜欢我的文章,那么

在看和转发是对我最大的支持!

(戳下面蓝字阅读)

433edb364d66f8d5088baf51ed07d708.png

推荐关注微信公众号:码侠江湖

                        e9a094f6a732c9aada82a83a248ff135.png觉得不错,点个在看再走哟

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/288736.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux之可视化查看磁盘大小并且删除大文件

1 问题 可视化查看磁盘大小并且删除大文件&#xff0c;之前我一直没有找到好的办法&#xff0c;原谅我的无知。 2 操作 我们直接搜索Disk Usage Analyzer 然后一个一个目的的点击查看&#xff0c;如下图 我们就可以清晰的看到文件占用大小&#xff0c;我们就可以很好的删除了…

php Heredoc应用说明

Heredoc部分实现界面与代码的准分离 我们如下的例子可以了解 Heredoc&#xff1a; <?php $name http://blog.csdn.net/a757291228; echo <<<HRERDOC <html> <head> <title>http://blog.csdn.net/a757291228</title> </head>…

Win10用户远超4亿 Win10 RS2明年年初发布

10月26日晚22:00&#xff0c;微软Win10新品发布会在纽约芝士举行。发布会前&#xff0c;微软宣布目前最新的操作系统Windows 10已经迎来了4亿的用户&#xff0c;Windows 10的使用小时数&#xff0c;已经达到了2000亿&#xff0c;游戏时间也增长了500%。 同时微软还宣布&#xf…

bootstrap导航

HTML <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IE-edge"><!-- 开启IE8渲染模式 --><meta name"viewport" cont…

一个类可以实现多个接口吗_Java入门:基础知识(面向对象:接口)

接着上一篇的基础知识&#xff0c;今天俺们来学习一下&#xff1a;面向对象(万物皆对象)三大特征(封装、继承、多态)接口接口 概述&#xff1a;接口是一种引用数据类型&#xff0c;是方法的集合&#xff0c;接口的内部主要是定义方法&#xff0c;包含常量、抽象方法(JDK7及以…

生成条形码二维码DataMatrix条码.EAN码.39码.交叉25码.UPC码.128码.93码.ISBN码.Codabar等...

1.引用Spire.Barcode在Nuget包中安装Spire.Barcode2.生成条形码//创建 BarcodeSettings对象BarcodeSettings settings new BarcodeSettings();//设置条形类型为EAN-13settings.Type BarCodeType.EAN13;//设置条形码数据settings.Data "58465157484";//使用校检set…

多云战略:企业如何精益求精?

随着为企业用户提供更多的选择和更高的灵活性&#xff0c;多云模式正在开始受到他们的关注。但正如实施一个单一的云部署一样&#xff0c;企业需要为多云计算实施进行精心评估&#xff0c;从而选择正确的云服务供应商。在某些情况下&#xff0c;那些市场的非主流供应商可能会为…

最常用的动态sql语句梳理Mybatis(转)

公司项目中一直使用Mybatis作为持久层框架&#xff0c;自然&#xff0c;动态sql写得也比较多了&#xff0c;最常见的莫过于在查询语句中使用if标签来动态地改变过滤条件了。Mybatis的强大特性之一便是它的动态sql&#xff0c;免除了拼接sql带来的各种麻烦&#xff0c;在开发项目…

用C++实现简单随机二元四则运算

让我们想看看二元四则运算都需要实现什么&#xff1a; &#xff08;1&#xff09; 定制题目数量 &#xff08;2&#xff09; 是否有乘除法 &#xff08;3&#xff09; 题目数值范围 &#xff08;4&#xff09; 加减有无负数 &#xff08;5&#xff09; 除法有无余数 &#xff0…

WireShark抓包之提示Alert Level: Fatal, Description: HandShake Failure

1 问题 ssl协议失败的方法,发了client hello包之后回复server hello包失败 2 分析 对比正常client hello的数据包, 我们点击Client Hello包看下详细信息,如下图 感觉ssl协议版本不对 然后我们修改了apache的配置,让代理服务

java地址映射关系,Spring MVC——基础(简介,使用,地址映射)

“大佬们”嘴中的SSH,SSM框架&#xff0c;我这种小白终于解除到第二个S了&#xff0c;关于Spring MVC框架&#xff0c;根据最近的学习发现&#xff0c;还是有很多不足和需要加强巩固的地方&#xff0c;所以&#xff0c;通过总结博客的方式将Spring MVC再次巩固学习一下。Spring…

scrapy爬取动态网页_scrapy_splash 设置随机请求头

本文为 霾大&#xff1a;scrapy_splash 爬取 js 加载网页初体验​zhuanlan.zhihu.com的补充在上面的文章中我们仅仅是初步完成了 scrapy_splash 的简单使用接下来我们将介绍如何是使得 splash 在 render.html &#xff08;默认&#xff09;访问网页时也能动态调整其请求头等&am…

机器学习模型开发必读:开源数据库最全盘点

开发 AI 和机器学习系统从来没有像现在这样方便。类似于 TensorFlow、Torch 和 Spark 这样的开源工具&#xff0c;在 AI 开发者群体中已是无处不在。再加上亚马逊 AWS、Google Cloud 等云服务带来的海量计算能力&#xff0c;将来使用笔记本电脑来训练 ML 模型或许不再难以想象。…

php之clone 复制对象以及__clone魔术方法

如果错误和不足请给予指出&#xff0c;谢谢~ (⊙&#xff3f;⊙) 在开始使用clone之前我们下先看以下一个小例子&#xff1a; <?php //首先定义一个test一个类 class Testclass {//成员变量是$value1public $value1; } //随后new一个obj1 $obj1 new Testclass(); //复…

C#中使用WeiFenLuo.WinFormsUI.Docking.dll实现窗口停靠效果

很酷的效果&#xff0c;很值得好好去学习的哈。 重置工具箱&#xff1a; 新建一个WinForm程序,项目名称为TestDockPanelControl。选中Form1窗体后选择工具箱--->>新建个添加选项卡命名为WeiFenLuo--->>右键--->>选择项--->>浏览--- >>weiFenLuo.…

使用aspnetcore前后端分离开发,你一定要知道这个。

前言用过Vue单页面应用开发的&#xff0c;一定都知道Vue-router这个路由组件&#xff0c;它支持hash和history两种模式。HTML5 History 模式vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL&#xff0c;于是当 URL 改变时&#xff0c;页面不会重新加载。…

Visual Studio 2015专业版创建Win32控制台应用程序,C,C++源文件

原配的Visual Studio 2015专业版不像之前的版本,在新建项目里面是找不到Win32模板的,那么怎么才能新建Win32项目和Win32控制台应用程序呢?今天我就带大家做一简单介绍。 先看如下图所示: 解决: 1、添加的办法是点击上图所示的新建项目界面的Visual C++下面的Windows,再…

php 序列化对象

习惯性借用手册里面的介绍&#xff1a; 所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。序列化一个对象将会保存对象的所有变量&#xff0c;但是不会保存对象的方法&#xff0c;只会保存类…

如何为同一 DTO 属性指定 2 个名称

前言我们在《实现DDD领域驱动设计》中谈到过输出 DTO 的最佳实践&#xff0c;其中一条是&#xff1a;保持输出 DTO 数量最少&#xff0c;尽可能重用。但是&#xff0c;对于 2 个不同接口输出的同一 DTO 属性&#xff0c;客户端可能需要对应不同的名称&#xff0c;比如&#xff…

HDU 4777 Rabbit Kingdom 树状数组

分析&#xff1a;找到每一个点的左边离他最近的不互质数&#xff0c;记录下标(L数组)&#xff0c;右边一样如此&#xff08;R数组&#xff09;&#xff0c;预处理 这个过程需要分解质因数O&#xff08;n*sqrt(n)) 然后离线&#xff0c;按照区间右端点排序 然后扫一遍&#xff0…