這篇隨筆主要記錄一下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)。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
總結一下整個流程:
- 當我們注冊一個服務時,最自然是通過它的類型和它的實現類型來注冊,比如IEmail類型和Outlook類型,所以要用到ServiceDescriptor;
- ServiceDescriptor包裝一下,搖身一變成為Service,并且得到了一個關鍵方法CreateCallSite();
- 為什么要callsite這種東西,主要是為了配合Provider管理服務的生命周期,以及實現一些特殊的解析服務的功能。如上所述,callsite的Invoke()得到ResolvedService,callsite的Build()方法得到RealizedService;
- 由Provider根據生命周期負責回收服務。
文章列表