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根據生命週期負責回收服務。
相關文章
相關標籤/搜索