[ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
包含服务注册信息的IServiceCollection集合最终被用来创建作为依赖注入容器的IServiceProvider对象。当需要消费某个服务实例的时候,我们只需要指定服务类型调用IServiceProvider的GetService方法即可,IServiceProvider对象就会根据对应的服务注册提供所需的服务实例。
一、IServiceProvider
如下面的代码片段所示,IServiceProvider接口定义了唯一的GetService方法根据指定的类型来提供对应的服务实例。当利用包含服务注册的IServiceCollection对象创建出IServiceProvider对象之后,我们只需要将服务注册的服务类型(对应于ServiceDescriptor的ServiceType属性)作为参数调用GetService方法,该方法就能根据服务注册信息为我们提供对应的服务实例。
public interface IServiceProvider
{
object GetService(Type serviceType);
}
针对IServiceProvider对象的创建体现在IServiceCollection接口的三个BuildServiceProvider扩展方法重载上。如下的代码片段所示,这三个扩展方法提供的都是一个类型为ServiceProvider的对象,该对象根据提供的配置选项来创建。配置选项类型ServiceProviderOptions提供了两个属性,其中ValidateScopes属性表示是否需要开启针对服务范围的验证,而ValidateOnBuild属性则表示是否需要预先检验作为服务注册的每个ServiceDescriptor对象能否提供对应的服务实例。默认情况下这两种类型的检验都是关闭的。
public class ServiceProviderOptions
{
public bool ValidateScopes { get; set; }
public bool ValidateOnBuild { get; set; }
internal static readonly ServiceProviderOptions Default = new ServiceProviderOptions();
} public static class ServiceCollectionContainerBuilderExtensions
{
public static ServiceProvider BuildServiceProvider( this IServiceCollection services)
=> BuildServiceProvider(services, ServiceProviderOptions.Default);
public static ServiceProvider BuildServiceProvider( this IServiceCollection services, bool validateScopes)
=> services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes }); public static ServiceProvider BuildServiceProvider( this IServiceCollection services, ServiceProviderOptions options)
=> new ServiceProvider(services, options);
}
虽然调用IServiceCollection的BuildServiceProvider扩展方法返回总是一个ServiceProvider对象,但是我并不打算详细介绍这个类型,这是因为ServiceProvider涉及到一系列内部类型和接口,并且实现在该类型中针对服务实例的提供机制一直在不断的变化,而且这个变化趋势在未来版本更替过程中可能还将继续下去。
除了定义在IServiceProvider接口中的GetService方法,该接口还具有如下这些扩展方法来提供服务实例。GetService<T>方法以泛型参数的形式指定了服务类型,返回的服务实例也会作对应的类型转换。如果指定服务类型的服务注册不存在,GetService方法会返回Null,如果调用GetRequiredService或者GetRequiredService<T>方法则会抛出一个InvalidOperationException类型的异常。如果所需的服务实例是必需的,我们一般会调用这两个扩展方法。
public static class ServiceProviderServiceExtensions
{
public static T GetService<T>(this IServiceProvider provider); public static T GetRequiredService<T>(this IServiceProvider provider);
public static object GetRequiredService(this IServiceProvider provider, Type serviceType); public static IEnumerable<T> GetServices<T>(this IServiceProvider provider);
public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType);
}
正如前面多次提到的,如果针对某个类型添加了多个服务注册,那么GetService方法总是会采用最新添加的服务注册来提供服务实例。如果希望利用所有的服务注册来创建一组服务实例列表,我们可以调用GetServices或者GetServices<T>方法,也可以调用GetService<IEnumerable<T>>方法。
二、服务实例的创建
对于通过调用IServiceCollection集合的BuildServiceProvider方法创建的IServiceProvider对象来说,当我们通过指定服务类型调用其GetService方法以获取对应的服务实例的时候,它总是会根据提供的服务类型从服务注册列表中找到对应的ServiceDescriptor对象,并根据它来提供所需的服务实例。
ServiceDescriptor具有三个不同的构造函数,分别对应着服务实例最初的三种提供方式,我们可以提供一个Func<IServiceProvider, object>对象作为工厂来创建对应的服务实例,也可以直接提供一个创建好的服务实例。如果我们提供的是服务的实现类型,那么最终提供的服务实例将通过调用该类型的某个构造函数来创建,那么构造函数是通过怎样的策略被选择出来的呢?
如果IServiceProvider对象试图通过调用构造函数的方式来创建服务实例,传入构造函数的所有参数必须先被初始化,所以最终被选择出来的构造函数必须具备一个基本的条件,那就是IServiceProvider能够提供构造函数的所有参数。为了让读者朋友能够更加深刻地理解IServiceProvider在构造函数选择过程中采用的策略,我们会采用实例演示的方式对此进行讲述。
我们在一个控制台应用中定义了四个服务接口(IFoo、IBar、IBaz和IGux)以及实现它们的四个类(Foo、Bar、Baz和Gux)。如下面的代码片段所示,我们为Gux定义了三个构造函数,参数均为我们定义了服务接口类型。为了确定IServiceProvider最终选择哪个构造函数来创建目标服务实例,我们在构造函数执行时在控制台上输出相应的指示性文字。
public interface IFoo {}
public interface IBar {}
public interface IBaz {}
public interface IGux {} public class Foo : IFoo {}
public class Bar : IBar {}
public class Baz : IBaz {}
public class Gux : IGux
{
public Gux(IFoo foo) => Console.WriteLine("Selected constructor: Gux(IFoo)");
public Gux(IFoo foo, IBar bar) => Console.WriteLine("Selected constructor: Gux(IFoo, IBar)");
public Gux(IFoo foo, IBar bar, IBaz baz) => Console.WriteLine("Selected constructor: Gux(IFoo, IBar, IBaz)");
}
在如下这段演示程序中我们创建了一个ServiceCollection对象并在其中添加针对IFoo、IBar以及IGux这三个服务接口的服务注册,针对服务接口IBaz的注册并未被添加。我们利用由它创建的IServiceProvider来提供针对服务接口IGux的实例,究竟能否得到一个Gux对象呢?如果可以,它又是通过执行哪个构造函数创建的呢?
class Program
{
static void Main()
{
new ServiceCollection()
.AddTransient<IFoo, Foo>()
.AddTransient<IBar, Bar>()
.AddTransient<IGux, Gux>()
.BuildServiceProvider()
.GetServices<IGux>();
}
}
对于定义在Gux中的三个构造函数来说,由于创建IServiceProvider提供的IServiceCollection集合包含针对接口IFoo和IBar的服务注册,所以它能够提供前面两个构造函数的所有参数。由于第三个构造函数具有一个类型为IBaz的参数,这无法通过IServiceProvider对象来提供。根据我们前面介绍的第一个原则(IServiceProvider对象能够提供构造函数的所有参数),Gux的前两个构造函数会成为合法的候选构造函数,那么IServiceProvider最终会选择哪一个呢?
在所有合法的候选构造函数列表中,最终被选择出来的构造函数具有这么一个特征:每一个候选构造函数的参数类型集合都是这个构造函数参数类型集合的子集。如果这样的构造函数并不存在,一个InvalidOperationException类型的异常会被抛出来。根据这个原则,Gux的第二个构造函数的参数类型包括IFoo和IBar,而第一个构造函数仅仅具有一个类型为IFoo的参数,最终被选择出来的会是Gux的第二个构造函数,所以运行我们的实例程序将会在控制台上产生如下图所示的输出结果。
接下来我们对实例程序略加改动。如下面的代码片段所示,我们只为Gux定义两个构造函数,它们都具有两个参数,参数类型分别为IFoo & IBar和IBar & IBaz。我们将针对IBaz / Baz的服务注册添加到创建的ServiceCollection集合中。
class Program
{
static void Main()
{
new ServiceCollection()
.AddTransient<IFoo, Foo>()
.AddTransient<IBar, Bar>()
.AddTransient<IBaz, Baz>()
.AddTransient<IGux, Gux>()
.BuildServiceProvider()
.GetServices<IGux>();
}
} public class Gux : IGux
{
public Gux(IFoo foo, IBar bar) {}
public Gux(IBar bar, IBaz baz) {}
}
对于Gux的两个构造函数,虽然它们的参数均能够由IServiceProvider对象来提供,但是并没有一个构造函数的参数类型集合能够成为所有有效构造函数参数类型集合的超集,所以IServiceProvider无法选择出一个最佳的构造函数。运行该程序后会抛出如下图所示的InvalidOperationException异常,并提示无法从两个候选的构造函数中选择出一个最优的来创建服务实例。(S409)
[ASP.NET Core 3框架揭秘] 依赖注入[1]:控制反转
[ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
[ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
[ASP.NET Core 3框架揭秘] 依赖注入[4]:一个迷你版DI框架
[ASP.NET Core 3框架揭秘] 依赖注入[5]:利用容器提供服务
[ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
[ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
[ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
[ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费的更多相关文章
- [ASP.NET Core 3框架揭秘] 依赖注入:控制反转
ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务
毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
生命周期决定了IServiceProvider对象采用怎样的方式提供和释放服务实例.虽然不同版本的依赖注入框架针对服务实例的生命周期管理采用了不同的实现,但总的来说原理还是类似的.在我们提供的依赖注入 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
.NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已.承载系统总是采用依赖注入的方式来消费它在服务承载过 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
<服务注册>.<服务消费>和<生命周期>主要从实现原理的角度对.NET Core的依赖注入框架进行了介绍,接下来更进一步,看看该框架的总体设计和实现.在过去的多个版 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
通过<利用容器提供服务>我们知道作为依赖注入容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架
在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照"好莱坞法则"实现应用程序的代码与框架之间的交互.我们可以采用若干设计模式 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
正如我们在<依赖注入:控制反转>提到过的,很多人将IoC理解为一种"面向对象的设计模式",实际上IoC不仅与面向对象没有必然的联系,它自身甚至算不上是一种设计模式.一般 ...
随机推荐
- Nginx+SpringBoot实现负载均衡
前言 在上一篇中介绍了Nginx的安装,本篇文章主要介绍的是Nginx如何实现负载均衡. 负载均衡介绍 介绍 在介绍Nginx的负载均衡实现之前,先简单的说下负载均衡的分类,主要分为硬件负载均衡和软件 ...
- day 23 复习
本来应该学习day23,由于上午未学习,下去困,导致今天未进行进度 那就做一下简单的复习吧! 1. while else结构,如果while 后的条件条件不再满足 引发循环再继续,则执行else中的内 ...
- 转载 :c# 获取net framework 版本(Environment 类)
源链接:http://www.cnblogs.com/tobecrazy/p/3362446.html 根据自己使用情况,添加了部分备注 1.获取当前操作系统版本信息 使用Environment.OS ...
- JS使用readAsDataURL读取图像文件
JS使用readAsDataURL读取图像文件 FileReader对象的readAsDataURL方法可以将读取到的文件编码成Data URL.Data URL是一项特殊的技术,可以将资料(例如图片 ...
- 高逼格利器之Python闭包与装饰器
生活在魔都的小明,终于攒够了首付,在魔都郊区买了一套房子:有一天,小明踩了狗屎,中了一注彩票,得到了20w,小明很是欢喜,于是想干脆用这20万来装修房子吧(decoration): 整个装修过程,小明 ...
- 图的存储结构与操作--C语言实现
图(graph)是一种比树结构还要复杂的数据结构,它的术语,存储方式,遍历方式,用途都比较广,所以如果想要一次性完成所有的代码,那代码会非常长.所以,我将分两次来完成图的代码.这一次,我会完成图的五种 ...
- uni-app中使用scroll-view滚到底部时多次触发scrolltolower事件
一.前言.scroll-view基本属性: 前言: 前段时间使用scroll-view可滚动视图区域容器来做多个不同内容的展示(在我这个页面中同时使用了三个scroll-view做数据展示),因为这几 ...
- 如何在DevOps中实施连续测试
在过去的十年中,对软件开发的需求已急剧发展.软件已成为公司获得竞争优势的关键优势,特别是如果您的公司属于SaaS范畴.通过在SDLC中实施瀑布等传统流程,组织现在正在向敏捷过渡,以便以更快的速度在市场 ...
- 【nodejs原理&源码赏析(5)】net模块与通讯的实现
[摘要] Node.js net模块的原理及使用 示例代码托管在:http://www.github.com/dashnowords/blogs 一. net模块简介 net模块是nodejs通讯功能 ...
- Python爬虫实战之爬取糗事百科段子
首先,糗事百科大家都听说过吧?糗友们发的搞笑的段子一抓一大把,这次我们尝试一下用爬虫把他们抓取下来. 友情提示 糗事百科在前一段时间进行了改版,导致之前的代码没法用了,会导致无法输出和CPU占用过高的 ...