这篇随笔主要记录一下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根据生命周期负责回收服务。

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. [Linux] USB-Storage驱动 源码阅读笔记(一)

    USB-Storage驱动 源码阅读笔记--从USB子系统开始 最近在研究U盘的驱动,遇到很多难以理解的问题,虽然之前也参考过一些很不错的书籍如:<USB那些事>,但最终还是觉得下载一份最 ...

  5. Transformers包tokenizer.encode()方法源码阅读笔记

    Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode

  6. 源码阅读笔记 BiLSTM+CRF做NER任务 流程图

    源码阅读笔记 BiLSTM+CRF做NER任务(二) 源码地址:https://github.com/ZhixiuYe/NER-pytorch 本篇正式进入源码的阅读,按照流程顺序,一一解剖. 一.流 ...

  7. 代码分析:NASM源码阅读笔记

    NASM源码阅读笔记 NASM(Netwide Assembler)的使用文档和代码间的注释相当齐全,这给阅读源码 提供了很大的方便.按作者的说法,这是一个模块化的,可重用的x86汇编器, 而且能够被 ...

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

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

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

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

最新文章

  1. 多线程与并发编程实践
  2. puppy linux不识别鼠标,不止于OS X!还有适用于Mac的八款替代操作系统
  3. 如何在Windows 10中将您喜欢的设置固定到开始菜单
  4. java flux api,SpringBoot学习系列-WebFlux REST API 全局异常处理
  5. 6年20多篇重磅论文,27岁浙大女博导太飒了~
  6. 处理veh调试器检测_越狱检测抖音逻辑???
  7. CTF之Web训练后篇2
  8. phoenix 根据条件更新_教您一步步升级Phoenix BIOS
  9. 网易云音乐——网易云云盘上传音乐自动改名的解决方法
  10. JUCE 中的音频编解码
  11. 算法基础 计算机数学课件,第4章计算机制图数学基础_中国地质大学:数字测图_ppt_大学课件预览_高等教育资讯网...
  12. 沉淀 2017,记录不平凡的一年!
  13. 合肥工业大学计算机网络期中考试,计算机网络实验报告合肥工业大学
  14. 笨方法学python 习题26
  15. Laravel + Elasticsearch 实现中文搜索
  16. aps是什么意思_aps画幅是什么意思
  17. java基础学习——Swing图形化用户界面编程
  18. Swift学习笔记 (四十二) 不透明类型
  19. SQL中IF函数的使用
  20. c语言判断字符是否对称,2020-07-23(C语言)数据结构-试设计算法判断该链表的全部n个字符是否中心对称。...

热门文章

  1. golang *time.Time类型转*timestamppb.Timestamp
  2. js赋值与逻辑运算的疑问
  3. Vue路由history模式踩坑记录:nginx配置解决404问题
  4. laravel 中间件中返回视图
  5. centos6.5 python2.6.6升级到python2.7.15
  6. sublime text 3 插件推荐?
  7. 微信小程序通用功能设计和实现
  8. Dubbo源码分析系列-深入RPC协议扩展
  9. 通过QEMU-GuestAgent实现从外部注入写文件到KVM虚拟机内部
  10. 使用 PDB 避免 Kubernetes 集群中断