ServiceProvider实现揭秘 【总体设计 】

本系列前面的文章我们主要以编程的角度对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实现揭秘【补充漏掉的细节】

ServiceProvider实现的更多相关文章

  1. Laravel Composer and ServiceProvider

    Composer and: 创建自定义类库时,按命名空间把文件夹结构组织好 composer.json>autoload>classmap>psr-4 composer dump-a ...

  2. ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起

    我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内 ...

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

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

  4. ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【解读ServiceCallSite 】

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

  5. 一种laravel特有的serviceProvider的加载方式

    这里的laravel版本5.5. 我是使用到dingo这个包的时候,觉得很奇怪,我们一般的包使用的时候都需要加载一个serviceProvider,提供服务,dingo/api这里也有ServiceP ...

  6. Core官方DI解析(2)-ServiceProvider

    ServiceProvider ServiceProvider是我们用来获取服务实例对象的类型,它也是一个特别简单的类型,因为这个类型本身并没有做什么,其实以一种代理模式,其核心功能全部都在IServ ...

  7. DependencyInjection源码解读之ServiceProvider

    var services = new ServiceCollection(); var _serviceProvider = services.BuildServiceProvider(); serv ...

  8. laravel中facade serviceprovider的理解

    一个serviceprovider就是一个解决某个功能的公用模块,实际上可以直接用在di里注册然后从di中取出,为啥还要搞个facade呢? 有几个方面的原因 1.把实例化移入到serviceprov ...

  9. laravel中的Contracts, ServiceContainer, ServiceProvider, Facades关系

    Contracts, ServiceContainer, ServiceProvider, Facades  Contracts 合同,契约,也就是接口,定义一些规则,每个实现此接口的都要实现里面的方 ...

随机推荐

  1. 怎样查看apk须要支持的Android版本号

    假设有一个apk,须要知道他最低安装支持的Android版本号是什么,应该怎样查看呢? 直接将apk后缀名改为rar或者zip,拉出AndroidManifest.xml?不行,AndroidMani ...

  2. mysql出现Waiting for table metadata lock的原因及解决方案

    最近经常遇到mysql数据库死锁,郁闷死, show processlist; 时 Waiting for table metadata lock 能一直锁很久 下面有官网的一段话,可以理解下 htt ...

  3. Servlet过滤器——日志记录过滤器

    1.概述 在实际的项目开发过程中,经常需要在项目运行时,记录并在控制台中输出运行时的日志信息,便于查看项目的运行状况.本实例将介绍如何应用过滤器实现日志记录.运行本实例,将在控制台中输出项目运行时的日 ...

  4. hdu 4507 数位dp(求和,求平方和)

    http://acm.hdu.edu.cn/showproblem.php?pid=4507 Problem Description 单身! 依旧单身! 吉哥依旧单身! DS级码农吉哥依旧单身! 所以 ...

  5. 屏幕编程 F4的帮组用法

    PROCESS ON VALUE-REQUEST. * 设置帮助(工作中心)  FIELD wa_zppt026-arbpl MODULE mdl_arbpl_f4. *&---------- ...

  6. javacsript (十) 数据类型

    类型声明的时候,直接使用 var  varname=xxx 数字.字符.布尔,声明都直接使用var就可以, JavaScript 数组 下面的代码创建名为 cars 的数组: var cars=new ...

  7. mysql sql执行顺序

    <pre name="code" class="html">mysql> explain select * from (select * fr ...

  8. Swift - 基本数据类型,及常/变量声明

    2015-01-08 14:59 发布:yuhang 浏览:434 下面是Swift中基本的数据类型介绍说明: 1,变量:使用var声明 1 var str:String = "hangge ...

  9. Ajax 下拉列表联动显示

    一般处理程序文件 代码 using System;using System.Web;using System.Linq;using System.Data.Linq;using System.Text ...

  10. 一个跨平台的 C++ 内存泄漏检测器

    2004 年 3 月 01 日 内存泄漏对于C/C++程序员来说也可以算作是个永恒的话题了吧.在Windows下,MFC的一个很有用的功能就是能在程序运行结束时报告是否发生了内存泄漏.在Linux下, ...