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

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

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

    要实现是一个依赖注入容器,主要是实现它添加依赖、描述依赖、存储依赖和解析依赖的能力,能够分别用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为节点的单向链表。ide

 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的形式,按照服务的类别存储实现了服务的相应类型(普通类,泛型类,委托等)。ui

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

    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团队提供的默认的依赖注入容器。spa

 

 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根据生命周期负责回收服务。
相关文章
相关标签/搜索