包含服务注册信息的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]:服务消费的更多相关文章

  1. [ASP.NET Core 3框架揭秘] 依赖注入:控制反转

    ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样 ...

  2. [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务

    毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容 ...

  3. [ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期

    生命周期决定了IServiceProvider对象采用怎样的方式提供和释放服务实例.虽然不同版本的依赖注入框架针对服务实例的生命周期管理采用了不同的实现,但总的来说原理还是类似的.在我们提供的依赖注入 ...

  4. [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

    .NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已.承载系统总是采用依赖注入的方式来消费它在服务承载过 ...

  5. [ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述

    <服务注册>.<服务消费>和<生命周期>主要从实现原理的角度对.NET Core的依赖注入框架进行了介绍,接下来更进一步,看看该框架的总体设计和实现.在过去的多个版 ...

  6. [ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册

    通过<利用容器提供服务>我们知道作为依赖注入容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创 ...

  7. [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架

    在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类 ...

  8. [ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照"好莱坞法则"实现应用程序的代码与框架之间的交互.我们可以采用若干设计模式 ...

  9. [ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式

    正如我们在<依赖注入:控制反转>提到过的,很多人将IoC理解为一种"面向对象的设计模式",实际上IoC不仅与面向对象没有必然的联系,它自身甚至算不上是一种设计模式.一般 ...

随机推荐

  1. IDEA用Maven连接MySQL的jdbc驱动,并操作数据库

    1.在IDEA里创建Maven项目 1.1.点击Create New Project   1.2.选择Maven,JDK这里用的是1.8,点击Next  1.3.填入“组织名”.“项目名”,版本是默认 ...

  2. tensorflow多层CNN代码分析

    tf,reshape(tensor,shape,name=None) #其中shape为一个列表形式,特殊的一点是列表中可以存在-1.-1代表的含义是不用我们自己#指定这一维的大小,函数会自动计算,但 ...

  3. Socket 实现简单的多线程服务器程序

    **********服务器端************* public class ServerSocket{ public static void main(String[] args) throws ...

  4. CentOS 7 Cobbler 自动化安装系统

    在上一篇Cobbler 安装中,配置好了Cobbler,下面来配置自动化安装 配置cobbler-DHCP # 修改settings中参数,由cobbler控制dhcp [root@cobbler ~ ...

  5. C#使用Consul集群进行服务注册与发现

    前言 我个人觉得,中间件的部署与使用是非常难记忆的:也就是说,如果两次使用中间件的时间间隔比较长,那基本上等于要重新学习使用. 所以,我觉得学习中间件的文章,越详细越好:因为,这对作者而言也是一份珍贵 ...

  6. 关于使用Java Mail发邮件的问题

    今天做东西的时候突然遇到需要发邮件的问题,然后就使用SMTP协议进行邮件的发送.用了一个工具类简化邮件发送的功能, 在这次试验中,我使用了自己的QQ邮箱进行发送邮件的发送者. public class ...

  7. PAT-2019年秋季考试-甲级

    7-1 Forever (20 分) #include <bits/stdc++.h> using namespace std; int N,K,m,number[10]; multima ...

  8. 最强Java并发编程详解:知识点梳理,BAT面试题等

    本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...

  9. 15 个优秀开源的 Spring Boot 学习项目,一网打尽!

    Spring Boot 算是目前 Java 领域最火的技术栈了,松哥年初出版的 <Spring Boot + Vue 全栈开发实战>迄今为止已经加印了 8 次,Spring Boot 的受 ...

  10. ZOJ 3195 Design the city (LCA 模板题)

    Cerror is the mayor of city HangZhou. As you may know, the traffic system of this city is so terribl ...