这篇随笔主要记录一下ASP.NET Core团队实现默认的依赖注入容器的过程,我的理解可能并不是正确的。

DependencyInjection这个项目不大,但却是整个ASP.NET Core的基础,因为它提供了依赖注入(DI)容器的默认实现,而依赖注入贯穿整个ASP.NET Core。相关源码可以去GitHub AspNet 上下载。

要实现是一个依赖注入容器,主要是实现它添加依赖、描述依赖、存储依赖和解析依赖的能力,可以分别用Add(A), Describe(D), Store(S), Resolve(R)表示。从功能的角度来讲,分别对应着 ServiceCollection,ServiceDescriptor,Service,ServiceEntry,ServiceTable,ServiceProvider, 以及CallSite相关的类。

对于框架使用者来说,注册一项服务最自然的方式就是提供服务的接口和实现这个接口的服务实例,比如IEmail是用户需求的服务,而Outlook类就是 服务的实例类型,用这两种信息注册一项服务是最自然的。所以ASP.NET Core团队提供了ServiceDescriptor类型来提供对服务的描述功能。

 1 public class ServiceDescriptor
 2  {  3 /// <inheritdoc />  4 public ServiceLifetime Lifetime { get; }  5  6 /// <inheritdoc />  7 public Type ServiceType { get; }  8  9 /// <inheritdoc /> 10 public Type ImplementationType { get; } 11 12 /// <inheritdoc /> 13 public object ImplementationInstance { get; } 14 15 /// <inheritdoc /> 16 public Func<IServiceProvider, object> ImplementationFactory { get; } 17 18 internal Type GetImplementationType(){...} 19 20 public static ServiceDescriptor Transient(){...} 21 public static ServiceDescriptor Singleton(){...} 22 public static ServiceDescriptor Scoped(){...} 23 }

可以看到ServiceDescriptor已经存储了服务的类型信息以及生命周期,貌似已经可以凭借着 Dictionary<ServiceType, ServiceDescriptor>存储所有的服务关系了。但有个问题,如果同一个服务注册了多个服务实例类型怎么办?比如IEmail服务同时 注册Outlook和GMail,该怎么存储,解析的时候又该用哪个?为了解决这个问题,ASP.NET Core团队提供了Service和ServiceEntry。不要以为Service是非常牛逼的类,其实它非常简单,Service就是一个存储 ServiceDescriptor的单向链表节点,而ServiceEntry就是以Service为节点的单向链表。

 1     internal class ServiceEntry
 2  {  3 private object _sync = new object();  4  5 public ServiceEntry(IService service)  6  {  7 First = service;  8 Last = service;  9  } 10 11 public IService First { get; private set; } 12 public IService Last { get; private set; } 13 14 public void Add(IService service) 15  { 16 lock (_sync) 17  { 18 Last.Next = service; 19 Last = service; 20  } 21  } 22 }

    internal class Service : IService{private readonly ServiceDescriptor _descriptor; public Service(ServiceDescriptor descriptor) { _descriptor = descriptor; } public IService Next { get; set; } public ServiceLifetime Lifetime { get { return _descriptor.Lifetime; } } public IServiceCallSite CreateCallSite(){...} }

从上面的源码可以看出Service类和ServiceEntry类就是一个典型的链表节点和链表的关系,Service类中还有一个很重要的方法是 CreateCallSite(),这是每个实现了IService的接口都要实现的方法。至于什么是callsite,之后会说到。

用ServiceEntry解决了一个服务的存储问题,自然一堆服务的存储就是用ServiceTable来存储。ServiceTable使用哈希表作 为底层容器,以ServiceType为Key,ServiceEntry为Value存储在Dictionary中。为了优化存储结构,缓存一些已经实 现过的服务,ServiceTable还添加了关于RealizedService的字段和方法。主要源码见下面:

   internal class ServiceTable{private readonly object _sync = new object(); private readonly Dictionary<Type, ServiceEntry> _services; private readonly Dictionary<Type, List<IGenericService>> _genericServices; private readonly ConcurrentDictionary<Type, Func<ServiceProvider, object>> _realizedServices = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>(); //注意ServiceTable只能被ServiceDescriptor的集合初始化 public ServiceTable(IEnumerable<ServiceDescriptor> descriptors){...} //省略了有关容器添加获取的方法 }

以上就是ASP.NET Core服务存储的相关过程,就实现来说,还是比较简单的,就是以K/V的形式,按照服务的类别存储实现了服务的相应类型(普通类,泛型类,委托等)。

仔细观察这些类型,你会发现它们都是internal级别的,那哪个才是公开类型呢?答案是ServiceCollection,这个类和Service 一样,看着很重要,其实就是一个ServiceDescriptor的List,因为它实现的接口继承了 IList<ServiceDescriptor>。

    public interface IServiceCollection : IList<ServiceDescriptor>{}public class ServiceCollection : IServiceCollection { //省略相关代码 }

ServiceCollection本质上是一个ServiceDescriptor的List,回忆一下,ServiceTable的构造函数正需要这 样的类型啊!那个这两个类又有什么关系,解开这个谜题的关键在于这整个解决方案真正的主角:ServiceProvider。我在这之前迟迟没有提到一个 依赖注入最关键的功能:解析依赖。对于一个服务A来说,它可能并不是独立的,它还在依赖服务B和服务C,而服务B又依赖服务D和服务E。。。一个合格的容 器得再我们需要服务A时,能够正确的解析这个依赖链,并按照正确的顺序实例化并返回服务A。ServiceProvider是ASP.NET Core团队提供的默认的依赖注入容器。

 1     internal class ServiceProvider : IServiceProvider, IDisposable
 2  {  3 private readonly ServiceProvider _root;  4 private readonly ServiceTable _table;  5 private bool _disposeCalled;  6  7 private readonly Dictionary<IService, object> _resolvedServices = new Dictionary<IService, object>();  8 private List<IDisposable> _transientDisposables;  9 10 private static readonly Func<Type, ServiceProvider, Func<ServiceProvider, object>> _createServiceAccessor = CreateServiceAccessor; 11 12 public ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors) 13  { 14 _root = this; 15 _table = new ServiceTable(serviceDescriptors); 16 17 _table.Add(typeof(IServiceProvider), new ServiceProviderService()); 18 _table.Add(typeof(IServiceScopeFactory), new ServiceScopeService()); 19 _table.Add(typeof(IEnumerable<>), new OpenIEnumerableService(_table)); 20  } 21 public object GetService(Type serviceType){...} 22 internal static Func<ServiceProvider, object> RealizeService(ServiceTable table, Type serviceType, IServiceCallSite callSite){...} 23 internal IServiceCallSite GetServiceCallSite(Type serviceType, ISet<Type> callSiteChain){...} 24 //省略了一些有关服务生存周期管理的方法以及一些其他私有方法 25 }

首先需要注意的是,它有一个ServiceTable类型的字段,所以一个ServiceProvider不仅是一个解析器,而且是一个容器,是一个依赖注入容器。第二点,仔细观察它的构造函数,你会发现它向table字段中添加了三个服务,而且这三个服务是自添加的,每个ServiceProvider都有。再研究一下这些服务的名字,更加有意思,ServiceProviderService!!也就是说ServiceProvider也是一种服务,解析服务也是一种服务,容器也是一种服务。这意味着我们可以使用其他依赖注入容器。第三点,也是最重要的一点,这个Service,RealizedService,ResolvedService以及我们一直避而不谈的callsite究竟是啥?

当我们以类型的方式描述一种服务时,它就是所谓的Service,这时它的信息全部以元数据的方式存储。

每一个Service都有一个CreateCallSite方法,所谓callsite,直接翻译是“调用点”,但更好的理解方式我觉得是元数据和服务 实例之间的桥梁,而如果一种Service元数据变成了Func<ServiceProvider, object>委托,我们就把它称为RealizedService,在Provider的table里面,有这么一个字段专门管理 RealizedService。那Func<ServiceProvider, object>委托又怎么理解呢?这种委托可以看作是服务的兑换券,它还不是解析的服务,但是离它很近了!因为只要把ServiceProvider传进去,我们就能得到解析过的Service。

如果把Func<ServiceProvider, object>委托当成兑换券,那么ServiceProvider就是兑换人,把兑换券拿给兑换人,我们就能得到object类型的服务,这种服 务称之为ResolvedService,在ServiceProvider中专门有一个字段缓存这些解析过的服务。callsite的Invoke(provider)方法得到一个服务实例(Resolved),而callsite的Build().Complie()方式可以得到Func<ServiceProvider, object>委托(Realized)。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

总结一下整个流程:

  1. 当我们注册一个服务时,最自然是通过它的类型和它的实现类型来注册,比如IEmail类型和Outlook类型,所以要用到ServiceDescriptor;
  2. ServiceDescriptor包装一下,摇身一变成为Service,并且得到了一个关键方法CreateCallSite();
  3. 为什么要callsite这种东西,主要是为了配合Provider管理服务的生命周期,以及实现一些特殊的解析服务的功能。如上所 述,callsite的Invoke()得到ResolvedService,callsite的Build()方法得到 RealizedService;
  4. 由Provider根据生命周期负责回收服务。

转载于:https://www.cnblogs.com/shiyajun/p/5577519.html

[转载]ASP.NET Core 源码阅读笔记(1) ---Microsoft.Extensions.DependencyInjection相关推荐

  1. ASP.NET Core 源码阅读笔记(1) ---Microsoft.Extensions.DependencyInjection

    这篇随笔主要记录一下ASP.NET Core团队实现默认的依赖注入容器的过程,我的理解可能并不是正确的. DependencyInjection这个项目不大,但却是整个ASP.NET Core的基础, ...

  2. ASP.NET Core 源码阅读笔记(5) ---Microsoft.AspNetCore.Routing路由

    这篇随笔讲讲路由功能,主要内容在项目Microsoft.AspNetCore.Routing中,可以在GitHub上找到,Routing项目地址. 路由功能是大家都很熟悉的功能,使用起来也十分简单,从 ...

  3. CI框架源码阅读笔记4 引导文件CodeIgniter.php

    到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http:// ...

  4. Yii源码阅读笔记 - 日志组件

    2015-03-09 一 By youngsterxyf 使用 Yii框架为开发者提供两个静态方法进行日志记录: Yii::log($message, $level, $category); Yii: ...

  5. AQS源码阅读笔记(一)

    AQS源码阅读笔记 先看下这个类张非常重要的一个静态内部类Node.如下: static final class Node {//表示当前节点以共享模式等待锁static final Node SHA ...

  6. 【Flink】Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型

    1.概述 转载:Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型 相似文章:[Flink]Flink 基于 MailBox 实现的 StreamTask 线程模型 Fl ...

  7. 【Flink】Flink 源码阅读笔记(18)- Flink SQL 中的流和动态表

    1.概述 转载:Flink 源码阅读笔记(18)- Flink SQL 中的流和动态表

  8. 【Flink】Flink 源码阅读笔记(16)- Flink SQL 的元数据管理

    1.概述 转载:Flink 源码阅读笔记(16)- Flink SQL 的元数据管理 Flink 源码阅读笔记(17)- Flink SQL 中的时间属

  9. 【Flink】Flink 源码阅读笔记(15)- Flink SQL 整体执行框架

    1.概述 转载:Flink 源码阅读笔记(15)- Flink SQL 整体执行框架 在数据处理领域,无论是实时数据处理还是离线数据处理,使用 SQL 简化开发将会是未来的整体发展趋势.尽管 SQL ...

最新文章

  1. oracle可视化工具IB,oracle小记:dba_data_files
  2. 高效能人士的七个习惯_《高效能人士的七个习惯》导图和读后感
  3. html霓虹灯效果图,HTML5 SVG+CSS3霓虹灯文字边框动画特效
  4. vscode 编辑器常用快捷键
  5. 江苏省计算机考试Python用书,【关注】Python列入高考内容以及全国计算机等级考试!...
  6. 开放接口加密方案_27种开放式解决方案,适用于所有教育
  7. 为什么要使用语义化标签
  8. ue4相机_UE4.24源码分析 - PlayerStart
  9. Sharding-JDBC 1.5.0.M1 正式发布,全新的 SQL 解析引擎
  10. js function定义函数的4种方法
  11. INDEX建立方式对SQL的影响
  12. Stopwatch 计时器类
  13. Tensorflow2.0学习(八) — tf.dataset自定义图像数据集
  14. Hive计算身份证年龄
  15. PreparedStatement的用法
  16. html使用表格输入数字,表格中输入数字,为什么出现“####”的原因
  17. 流程图设计(泳道图 | 任务流程图 | 页面流程图)
  18. 万字长文!面试官问你:自定义View跟绘制流程懂吗?帮你搞定面试官
  19. oracle 18c,Oracle 18c
  20. VMware 兼容性列表与产品互操作性列表使用收集(持续更新中...)

热门文章

  1. python入门到实践-Python编程从入门到实践(基础入门)
  2. python编程入门p-读书笔记 - 《Python编程:从入门到实践》
  3. redis中的quicklist
  4. LeetCode Additive Number(递归)
  5. Qt设置应用程序图标
  6. UVa11770 - Lighting Away(排序+DFS)
  7. stl的set,multiset, map, multimap, deque, list, stack, queue, priority_queue
  8. 听大佬学长RQY报告有感
  9. 第一课 计算机组成原理(哈工大)
  10. for循环:用turtle画一颗五角星