ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】

时间:2022-08-25 08:44:31

本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了。如果你还对这个依赖注入系统底层的实现原理具有好奇心,可以继续阅读这一节的内容。

目录
一、ServiceCallSite

二、Service

三、ServiceEntry

四、ServiceTable

五、ServiceProvider

作为DI容器的体现,ServiceProvider是ASP.NET Core依赖注入系统的一个核心对象,但是默认的实现者是一个定义在程序集 “Microsoft.Extensions.DependencyInjection.dll” 中的一个名为 “ServiceProvider” 内部(Internal)类型,而且它所依赖的很多接口和类型也是如此,所以我相信实现在这个ServiceProvider类中的服务提供机制对于绝大部分人是陌生的。本节提及的ServiceProvider不是泛指实现了IServiceProvider接口的类型,而是专指ServiceProvider这个内部类型。

为了让读者朋友们能够深刻地了解ServiceProvider内部的实现原理,我会在本节内容中重新定义它。在这里需要特别说明的是我们重建的ServiceProvider以及其他重建的接口和类旨在体现真实ServiceProvider设计思想和实现原理,在具体的源代码层面是有差异的。考虑到篇幅的问题,很多细节的内容将不会体现在我们重建的接口和类型中。如果想了解原始的实现逻辑,可以从GitHub上下载源代码。

从总体设计的角度来审视ServiceProvider,需要涉及与之相关的4个核心对象,包括ServiceCallSite、Service、ServiceEntry和ServiceTable,它们均体现为相应的接口和类,并且这些接口和泪都是内部的,接下来我们就来逐一认识它们。

一、ServiceCallSite

ServiceProvider的核心功能就是针对服务类型提供相应的服务实例,而服务实例的提供最终是通过ServiceCallSite来完成的。ServiceCallSite体现为具有如下定义的IServiceCallSite接口,除了直接提供服务实例的Invoke方法之外,它还具有另一个返回类型为Expression的Build方法,该方法将定义在Invoke方法中的逻辑定义成一个表达式。

   1: internal interface IServiceCallSite

   2: {

   3:     object Invoke(ServiceProvider provider);

   4:     Expression Build(Expression provider);

   5: }

在真正提供服务实例的时候,ServiceProvider在收到针对某个服务类型的第一个服务获取请求时,他会直接调用对应ServiceCallSite的Invoke方法返回提供的服务实例。与此同时,这个ServiceCallSite的Build方法会被调用并生成一个表达式,该表达式进一步编译成一个类型为Func<ServiceProvider, object>的委托对象并被缓存起来。针对同一个服务类型的后续服务实例将直接使用这个缓存的委托对象来提供。

二、Service

我们知道ServiceProvider提供服务的依据来源于创建它指定一个ServiceCollection对象,用于指导ServiceProvider如何提供所需服务的信息以ServiceDescriptor对象的形式保存在这个集合对象中。当ServiceProvider被初始化后,每一个ServiceDescriptor将会被转换成一个Service对象,后者体现为如下一个IService接口。

   1: internal interface IService

   2: {

   3:     IService Next { get; set; }

   4:     ServiceLifetime Lifetime { get; }

   5:     IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain);

   6: }

Service的Lifetime属性自然来源于ServiceDescriptor的同名属性,它的CreateCallSite方法返回一个针对用于提供对应服务实例的ServiceCallSite对象。由于Service对象可以创建ServiceCallSite,所以它自然具有提供服务实例的能力。Service总是作为链表的某个节点存在,这个链表是具有相同服务类型(对应ServiceType属性)的多个ServiceDescriptot生成的,Service的Next属性保持着对链表后一个节点的引用。

三、ServiceEntry

上面我们所说的由Service对象组成的链表体现为如下一个ServiceEntry类。我们为ServiceEntry定义了三个属性(First、Last、All)分别代笔这个链表的第一个节点、最后一个节点以及所有节点,节点类型为IService。如果需要在链尾追加一个Service对象,可以直接调用Add方法。

   1: internal class ServiceEntry

   2: {

   3:     public IService         First { get; private set; }

   4:     public IService         Last { get; private set; }

   5:     public IList<IService>     All { get; private set; } = new List<IService>();

   6:  

   7:     public ServiceEntry(IService service)

   8:     {

   9:         this.First = service;

  10:         this.Last = service;

  11:         this.All.Add(service);

  12:     }

  13:  

  14:     public void Add(IService service)

  15:     {

  16:         this.Last.Next = service;

  17:         this.Last = service;

  18:         this.Add(service);

  19:     }

  20: }

四、ServiceTable

多个ServiceEntry组成一个ServiceTable。如下面的代码片段所示,一个ServiceTable通过其只读属性ServieEntries维护着一组ServiceEntry对象与它们对应的服务类型之间的映射关系。一个ServiceTable对象通过一个ServiceCollection对象创建出来。如下面的代码片段所示,组成ServiceCollection的所有ServiceDescriptor对象先根据其ServiceType属性体现的服务类型进行分组,由每组ServiceDescriptor创建的ServiceEntry对象与对应的服务类型之间的映射会被添加到ServiceEntries属性中。

   1: internal class ServiceTable

   2: {

   3:     public IDictionary<Type, ServiceEntry> ServieEntries { get; private set; } = new Dictionary<Type, ServiceEntry>();

   4:  

   5:     public ServiceTable(IServiceCollection services)

   6:     {

   7:         foreach (var group in services.GroupBy(it=>it.ServiceType))

   8:         {

   9:             ServiceDescriptor[] descriptors = group.ToArray();

  10:             ServiceEntry entry = new ServiceEntry(new Service(descriptors[0]));

  11:             for (int index = 1; index < descriptors.Length; index++)

  12:             {

  13:                 entry.Add(new Service(descriptors[index]));

  14:             }

  15:             this.ServieEntries[group.Key] = entry;

  16:         }

  17:         //省略其他代码

  18:     }

  19: }

从上面的代码片段可以看出组成ServiceEntry的是一个类型为Service的对象,该类型定义如下。Service类实现了IService接口并通过一个ServiceDescriptor对象创建而成。我们省略了定义在方法CreateCallSite中创建ServiceCallSite的逻辑,后续在介绍各种类型的ServiceCallSite的时候我们会回来讲述该方法的实现。

   1: internal class Service : IService

   2: {

   3:     public ServiceDescriptor     ServiceDescriptor { get; private set; }

   4:     public ServiceLifetime         Lifetime => this.ServiceDescriptor.Lifetime;

   5:     public IService             Next { get; set; }

   6:  

   7:     public Service(ServiceDescriptor serviceDescriptor)

   8:     {

   9:         this.ServiceDescriptor = serviceDescriptor;

  10:     }

  11:  

  12:     public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)

  13:     {

  14:         <<省略实现>>

  15:     }

  16: }

五、ServiceProvider

如下所示的代码片段揭示了实现在ServiceProvider之中与服务提供和回收相关的基本实现原理。我们先来简单介绍定义在它内部的几个属性。Root属性返回的ServiceProvider代表它的根,对于一个独立的ServiceProvider来说,这个根就是它自己。ServiceTable属性返回根据ServiceCollection创建的ServiceTable对象。上面介绍ServiceCallSite的时候,我们提到它的Build方法返回的表达式会编译成一个类型为Func <ServiceProvider,object>的委托,并被缓存起来服务于后续针对同一个类型的服务提供请求,该委托对象与对应服务类型之间的映射关系就保存在RealizedServices属性中。

   1: internal class ServiceProvider : IServiceProvider, IDisposable

   2: {

   3:     public ServiceProvider Root { get; private set; }

   4:     public ServiceTable ServiceTable { get; private set; }

   5:     public ConcurrentDictionary<Type, Func<ServiceProvider, object>> RealizedServices { get; private set; } = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();

   6:     public IList<IDisposable> TransientDisposableServices { get; private set; } = new List<IDisposable>();

   7:     public ConcurrentDictionary<IService, object> ResolvedServices { get; private set; } = new ConcurrentDictionary<IService, object>();

   8:     

   9:     public ServiceProvider(IServiceCollection services)

  10:     {

  11:         this.Root         = this;

  12:         this.ServiceTable     = new ServiceTable(services);

  13:     }

  14:  

  15:     public object GetService(Type serviceType)

  16:     {

  17:         Func<ServiceProvider, object> serviceAccessor;

  18:         if (this.RealizedServices.TryGetValue(serviceType, out serviceAccessor))

  19:         {

  20:             return serviceAccessor(this);

  21:         }

  22:  

  23:         IServiceCallSite serviceCallSite = this.GetServiceCallSite(serviceType, new HashSet<Type>());

  24:         if (null != serviceCallSite)

  25:         {

  26:             var providerExpression = Expression.Parameter(typeof(ServiceProvider), "provider");

  27:             this.RealizedServices[serviceType] = Expression.Lambda<Func<ServiceProvider, object>>(serviceCallSite.Build(providerExpression), providerExpression).Compile();

  28:             return serviceCallSite.Invoke(this);

  29:         }

  30:  

  31:         this.RealizedServices[serviceType] = _ => null;

  32:         return null;

  33:     }

  34:  

  35:     public IServiceCallSite GetServiceCallSite(Type serviceType, ISet<Type> callSiteChain)

  36:     {

  37:             try

  38:             {

  39:                 if (callSiteChain.Contains(serviceType))

  40:                 {

  41:                     throw new InvalidOperationException(string.Format("A circular dependency was detected for the service of type '{0}'", serviceType.FullName);

  42:                 }

  43:                 callSiteChain.Add(serviceType);

  44:  

  45:                 ServiceEntry serviceEntry;

  46:                 if (this.ServiceTable.ServieEntries.TryGetValue(serviceType, 

  47:                     out serviceEntry))

  48:                 {

  49:                     return serviceEntry.Last.CreateCallSite(this, callSiteChain);

  50:                 }

  51:  

  52:                 //省略其他代码

  53:  

  54:                 return null;

  55:             }

  56:             finally

  57:             {

  58:                 callSiteChain.Remove(serviceType);

  59:             }

  60:     }    

  61:  

  62:     public void Dispose()

  63:     {

  64:         Array.ForEach(this.TransientDisposableServices.ToArray(), _ => _.Dispose());

  65:         Array.ForEach(this.ResolvedServices.Values.ToArray(), _ => (_ as IDisposable)?.Dispose());

  66:         this.TransientDisposableServices.Clear();

  67:         this.ResolvedServices.Clear();

  68:     }

  69:     //其他成员

  70: }

对于采用Scoped模式提供的服务实例,ServiceProvider需要自行对它们进行维护,具体来说它们会和对应的Service对象之间的映射关系会保存在ResolvedServices属性中。如果采用Transient模式,对于提供过的服务实例,如果自身类型实现了IDisposble接口,它们会被添加到TransientDisposableServices属性返回的列表中。当Dispose方法执行的时候,这两组对象的Dispose方法会被执行。

真正的服务提供机制体现在ServiceProvider实现的GetService方法中,实现逻辑其实很简单:ServiceProvider会根据指定的服务类型从RealizedServices属性中查找是否有通过编译表达式生成的Func<ServiceProvider,object>委托生成出来,如果存在则直接使用它生成提供的服务实例。如果这样的委托不存在,则会试着从ServiceTable中找到对应的ServiceEntry,如果不存在直接返回Null,否则会调用ServiceEntry所在列表最后一个Service的CreateServiceCallSite方法创建一个ServiceCallSite对象(这一点说明了如果针对同一个服务类型注册了多个ServiceDescriptor,在提供单个服务的时候总是使用最后一个ServiceDescriptor)。

接下来这个ServiceCallSite的Invoke方法被调用来创建服务实例,在返回该实例之前它的Build方法会被调用,返回的表达式被编译成Func<ServiceProvider,object>委托并被添加到RealizedServices属性中。如果ServiceProvider后续需要提供同类型的服务,这个委托对象将被启用。

ASP.NET Core中的依赖注入(1):控制反转(IoC)
ASP.NET Core中的依赖注入(2):依赖注入(DI)
ASP.NET Core中的依赖注入(3):服务注册与提取
ASP.NET Core中的依赖注入(4):构造函数的选择与生命周期管理
ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【总体设计】
ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【解读ServiceCallSite】
ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】

ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】的更多相关文章

  1. ASP&period;NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  2. ASP&period;NET Core中的依赖注入(2):依赖注入(DI)

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...

  3. ASP&period;NET Core中的依赖注入(3)&colon; 服务的注册与提供

    在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...

  4. ASP&period;NET Core中的依赖注入(4)&colon; 构造函数的选择与服务生命周期管理

    ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的,对于一个具体的ServiceDescriptor对象来说,如果它的ImplementationI ...

  5. ASP&period;NET Core中的依赖注入(5)&colon; ServiceProvider实现揭秘 【解读ServiceCallSite 】

    通过上一篇的介绍我们应该对实现在ServiceProvider的总体设计有了一个大致的了解,但是我们刻意回避一个重要的话题,即服务实例最终究竟是采用何种方式提供出来的.ServiceProvider最 ...

  6. ASP&period;NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】

    到目前为止,我们定义的ServiceProvider已经实现了基本的服务提供和回收功能,但是依然漏掉了一些必需的细节特性.这些特性包括如何针对IServiceProvider接口提供一个Service ...

  7. ASP&period;NET Core 中的依赖注入

    目录 什么是依赖注入 ASP .NET Core 中使用依赖注入 注册 使用 释放 替换为其它的 Ioc 容器 参考 什么是依赖注入 软件设计原则中有一个依赖倒置原则(DIP),为了更好的解耦,讲究要 ...

  8. ASP&period;NET Core 中的 依赖注入介绍

    ASP.NET Core 依赖注入 HomeController public class HomeController : Controller { private IStudentReposito ...

  9. ASP&period;NET Core 中的依赖注入 &lbrack;共7篇&rsqb;

    一.控制反转(IoC) ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了 ...

随机推荐

  1. Autodesk 360 Viewer 已经发布到Autodesk 360平台

    我们之前已经在小范围内透露过,Autodesk 将发布一款完全无需插件的三维模型浏览器 Autodesk 360 Viewer,比如我们的Meetup线下小聚会,或者黑客马拉松(hackathon)的 ...

  2. Coursera课程下载和存档计划&lbrack;转载&rsqb;

    上周三收到Coursera平台的群发邮件,大意是Coursera将在6月30号彻底关闭旧的课程平台,全面升级到新的课程平台上,一些旧的课程资源(课程视频.课程资料)将不再保存,如果你之前学习过相关的课 ...

  3. jQuery 效果 —— 隐藏和显示

    jQuery 效果 -- 隐藏和显示 1.隐藏和显示 (1)在jQuery中我们可以使用hide()和show()分别隐藏和显示HTML元素: //隐藏元素 $("button") ...

  4. hdu1695:数论&plus;容斥

    题目大意: 求x属于[1,b]和 y属于[1,d]的 gcd(x,y)=k 的方案数 题解: 观察发现 gcd()=k 不好处理,想到将x=x/k,y=y/k 后 gcd(x,y)=1.. 即问题转化 ...

  5. python爬虫从入门到放弃(三)之 Urllib库的基本使用

    官方文档地址:https://docs.python.org/3/library/urllib.html 什么是Urllib Urllib是python内置的HTTP请求库包括以下模块urllib.r ...

  6. 本地上传文件至服务器的技巧&lpar;linux文件压缩及解压文件&rpar;

    linux(ubuntu)文件解压及压缩文件 ubuntu支持文件的解压及压缩功能, 如果ubuntu上面没有安装过unzip工具的话,可以通过下面命令安装: sudo apt-get install ...

  7. 分布式监控系统(类zabbix)

    目录: 为什么要做监控? 监控系统业务需求分析: 监控系统架构设计: 监控系统表结构设计: 一.为什么要做监控系统? 市面上已经有很多成熟的监控系统,例如zabbix.nagios,为什么自己开发监控 ...

  8. swagger 常用注解说明

    本内容引用自:https://blog.csdn.net/u014231523/article/details/76522486 常用注解: - @Api()用于类: 表示标识这个类是swagger的 ...

  9. selected标签判断默认选中

    <select name="suggestedType" style="width:280px" > <option value=" ...

  10. Hotspot参数分析

    -XX:+HeapDumpOnOutOfMemoryError 让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析 -Xmx与-Xms 虚拟机堆参数 -Xoss 设置本地方法栈 ...