ServiceProvider实现
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实现的更多相关文章
- Laravel Composer and ServiceProvider
Composer and: 创建自定义类库时,按命名空间把文件夹结构组织好 composer.json>autoload>classmap>psr-4 composer dump-a ...
- ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起
我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内 ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】
本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【解读ServiceCallSite 】
通过上一篇的介绍我们应该对实现在ServiceProvider的总体设计有了一个大致的了解,但是我们刻意回避一个重要的话题,即服务实例最终究竟是采用何种方式提供出来的.ServiceProvider最 ...
- 一种laravel特有的serviceProvider的加载方式
这里的laravel版本5.5. 我是使用到dingo这个包的时候,觉得很奇怪,我们一般的包使用的时候都需要加载一个serviceProvider,提供服务,dingo/api这里也有ServiceP ...
- Core官方DI解析(2)-ServiceProvider
ServiceProvider ServiceProvider是我们用来获取服务实例对象的类型,它也是一个特别简单的类型,因为这个类型本身并没有做什么,其实以一种代理模式,其核心功能全部都在IServ ...
- DependencyInjection源码解读之ServiceProvider
var services = new ServiceCollection(); var _serviceProvider = services.BuildServiceProvider(); serv ...
- laravel中facade serviceprovider的理解
一个serviceprovider就是一个解决某个功能的公用模块,实际上可以直接用在di里注册然后从di中取出,为啥还要搞个facade呢? 有几个方面的原因 1.把实例化移入到serviceprov ...
- laravel中的Contracts, ServiceContainer, ServiceProvider, Facades关系
Contracts, ServiceContainer, ServiceProvider, Facades Contracts 合同,契约,也就是接口,定义一些规则,每个实现此接口的都要实现里面的方 ...
随机推荐
- 设计模式六大原则(2):里氏替换原则(Liskov Substitution Principle)
肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑.事实上原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的. 定义1:假设对每 ...
- 自定义NavgationBa返回按钮
iOS 上UINavigationController视图压栈形式,可以在当前视图无限制push许多视图,然而一些会觉得自带的push按钮不够美观,而且当上的上一个页面title很长的时候,那个返回 ...
- android onKeydown
package wyf.ytl; import android.app.Activity; import android.content.Context; import android.os.Bund ...
- MySQL 执行计划里的rows
<pre name="code" class="html">SQL> alter session set statistics_level=a ...
- (Android) Download Images by AsyncTask API
1. Check network status AndroidManifest.xml <uses-sdk> ... </> <uses-permission andro ...
- Cocos2d-X3.0 刨根问底(九)----- 场景切换(TransitionScene)源代码分析
上一章我们分析了Scene与Layer相关类的源代码,对Cocos2d-x的场景有了初步了解,这章我们来分析一下场景变换TransitionScene源代码. 直接看TransitionScene的定 ...
- 配置虚拟主机并更改Apache默认解析路径
配置虚拟主机,非常easy 改动以下文件: 加入以下几句话 <VirtualHost *:80> ##ServerAdmin webmaster@dummy-host2.example.c ...
- gcc和g++的区别【转自中国源码网】
gcc和g++的区别[转自中国源码网] gcc和g++都是GNU(组织)的一个编译器. 误区一:gcc只能编译c代码,g++只能编译c++代码两者都可以,但是请注意:1.后缀为.c的,gcc把它当作是 ...
- QT实现不规则窗体
看到网上有很多不规则窗体的实现,效果很酷.于是使用QT也实现了一个,QT的不规则窗体实现非常简单,只需要设置一个mask(遮掩)图片,这个图片的格式可以使用png或bmp格式,我使用了png格式,默认 ...
- (并查集)POJ 1308 & HDU 1325
一开始以为两道题是一样的,POJ的过了直接用相同代码把HDU的交了,结果就悲剧了.最后发现HDU的没有考虑入度不能大于一. 题意:用树的定义来 判断吧,无环,n个结点最多有n-1条边,不然就会有环.只 ...