本系列前面的文章我們主要以編程的角度對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實現揭秘【補充漏掉的細節】
文章列表