.netcore入門26:.net core源碼分析之依賴注入

環境:

  • window10
  • .netcore 3.1
  • vs2019 16.5.1

一、依賴注入說明

依賴注入的概念這裏就不說了,隨便百度一大堆。在aspnetcore框架中,依賴注入是一個很基礎的東西,所以我們必須要很熟悉纔行。但現在我們要講的依賴注入不在aspnetcore框架中,而是在.netcore中,因爲依賴注入相關的代碼都是在包:Microsoft.Extensions.DependencyInjection包:Microsoft.Extensions.DependencyInjection.Abstractions中的,aspnetcore框架只不過做了集成和一點擴展。

二、.netcore框架中的依賴注入特點

市面上有很多依賴注入框架(比如:spring、autofac、tinyioc等),他們都有共同的目的(即:解耦),但他們又都有自己的特點。.netcore中的依賴注入框架的特點是:簡單輕量、僅支持構造函數注入

2.1 簡單輕量

在.netcore控制檯工程中,你只需要安裝nuget包Microsoft.Extensions.DependencyInjection即可使用依賴注入,而這個包又僅依賴於包Microsoft.Extensions.DependencyInjection.Abstractions,這兩個包的代碼量非常少,所以比較輕量,看下圖:
在這裏插入圖片描述

2.2 僅支持構造函數注入

不像其他的框架(比如:spring、autofac)支持字段、屬性等多種方式注入,.netcore的依賴注入框架僅支持構造函數注入,我認爲:這種注入方式雖然單一,但它能提高我們代碼的可讀性。
看下面的示例,當我們需要容器內的什麼服務時,直接在構造函數中聲明即可:
在這裏插入圖片描述

三、服務的生命週期

在說服務的生命週期之前先來探討一個問題:當我們向依賴容器請求一個服務實例的時候,容器是新創建一個返回給我們?還是使用上次創建的實例返回給我們?
答:容器需要兼容這幾種情況,不能只考慮單一的情況。
所以,依賴容器就有了服務生命週期的概念,即:在向容器中註冊服務的時候,我們就應該聲明這個服務的生命週期,這樣當我們向容器請求服務實例的時候,容器就可以根據服務註冊時指定的生命週期採取不同的策略了。

依賴容器中的服務有三類生命週期:

  • Singleton(容器內單例)
    全容器內唯一,即:無論你何時向容器中請求這個服務實例,容器返回的都是同一個。
  • Scoped(範圍內單例)
    範圍內唯一。首先說下什麼是範圍:比如一次http請求,定時任務的一次調用執行等等。範圍內唯一就是說在這個範圍內,我們向容器請求服務實例,容器返回的總是同一個。不同的範圍可以有不同的服務實例。
  • Transient(瞬時,非單例)
    Transient是瞬時的,即:當我們任何時候向依賴容器請求服務實例的時候,容器都會新創建一個返回給我們。

依賴服務容器生命週期的實現原理:
我們可以根據一個依賴容器創建一個新的依賴容器(var scopedProvider = provider.CreateScope().ServiceProvider;),這樣就可以由一個依賴容器衍生出很多依賴容器,但是隻能有一個根容器(也就是使用ServiceCollection創建的依賴容器),其他的容器都是平級的,它們的關係如下圖所示:
在這裏插入圖片描述
從上圖我們應該能猜到服務生命週期的實現原理了,簡單來說:

  • 當我們向容器請求一個Singleton服務的時候,依賴容器從根容器中查找有沒有已經創建好的實例,如果沒有的話就新創建一個實例返回給我們並同時保存到根容器中。
  • 當我們向容器中請求一個Scoped服務的時候,依賴容器查找自己有沒有已經創建好的實例,如果沒有的話就新創建一個實例返回給我們並同時自己保存一份。
  • 當我們向容器請求一個Transient服務的時候,依賴容器直接新創建一個實例返回給我們(不做任何緩存)。

講到這裏,我們來思考1個問題:能不能從根容器中解析出Scoped類型的服務?

  • 認爲可以,此時根容器類似於其他的容器,它在自己內部緩存Scoped服務的實例。
  • 認爲不可以,此時根容器只能緩存Singleton服務實例,而Scoped服務只能由非根容器緩存,這也間接導致了Singleton服務不能依賴Scoped服務。

上面說的兩種情況都有道理,下面的更嚴格一些,但.netcore的依賴注入框架默認是第一種情況,即:認爲可以。我們可以在Build的時候改變這個默認:

static void Main(string[] args)
{
   var services = new ServiceCollection();
   services.AddScoped<Person>();
   var provider = services.BuildServiceProvider(new ServiceProviderOptions()
   {
       ValidateOnBuild = true,
       ValidateScopes = true
   });
}

上面的ValidateScopes表示的就是是否驗證Scoped服務必須從非根容器中解析出來,默認是false,這裏我們將它改爲true。ValidateOnBuild表示我們是否在容器Build的時候就進行驗證還是解析服務實例的時候再驗證?默認是false,這裏我們也設置爲true,一旦設置爲true後,容器在Build的時候就會檢查是否有Singleton服務依賴了Scoped服務,如果有的話就直接報異常(因爲Singleton服務依賴Scoped服務必然要引發根容器解析Scoped服務實例,除非你一直不向容器請求這個Singleton服務實例)。

在Build時驗證失敗的案例:
在Build服務容器的時候檢查Singleton服務不能依賴Scoped服務: 在這裏插入圖片描述
在解析服務實例時驗證失敗的案例:
在解析Singleton服務實例的時候檢查是否依賴到了Scoped服務:在這裏插入圖片描述

在解析Scoped服務實例時檢查是否是從根容器中解析的:在這裏插入圖片描述
依賴注入的正確使用方式:
我們應儘可能的避免從根容器中解析Scoped服務,即遵循嚴格模式(ValidateScopes=true),代碼示例如下:

namespace ConsoleApp7
{
    public class Teacher { }
    public class Book { }
    public class Stucent
    {
        public readonly Teacher teacher;
        public readonly Book book;
        public Stucent(Teacher teacher, Book book)
        {
            this.teacher = teacher;
            this.book = book;
        }
    }
    class Program
    {
        public static void Main(string[] args)
        {
            var services = new ServiceCollection();
            services.AddSingleton<Teacher>();
            services.AddSingleton<Book>();
            services.AddScoped<Stucent>();

            var provider = services.BuildServiceProvider(new ServiceProviderOptions()
            {
                ValidateScopes = true,
                ValidateOnBuild = true
            });
            var teacher = provider.GetService<Teacher>();
            var book = provider.GetService<Book>();

            var scopedProvider = provider.CreateScope().ServiceProvider;
            var student = scopedProvider.GetService<Stucent>();

            Console.WriteLine($"student.teacher==teacher=>{student.teacher == teacher}\r\nstudent.book==book=>{student.book == book}");

            Console.WriteLine("ok");
            Console.ReadLine();
        }
    }
}

運行效果如下:
在這裏插入圖片描述

四、循環依賴問題

這裏我們思考一個問題,如果我們向容器中註冊的服務存在循環依賴,那麼當我們請求容器解析的時候容器會怎麼辦?
比如我們定義了三個服務:Teacher、Book、Student,然而它們相互依賴如下:
在這裏插入圖片描述
那麼當我們請求服務實例的時候,容器肯定解析不了,直接給我們報個異常! 看下面的代碼:

namespace ConsoleApp7
{
   public class Teacher
   {
       public Teacher(Book book) { }
   }
   public class Stucent
   {
       public Stucent(Teacher teacher) { }
   }
   public class Book
   {
       public Book(Stucent stucent) { }
   }
   class Program
   {
       public static void Main(string[] args)
       {
           var services = new ServiceCollection();
           services.AddSingleton<Teacher>();
           services.AddSingleton<Stucent>();
           services.AddSingleton<Book>();

           var provider = services.BuildServiceProvider(new ServiceProviderOptions()
           {
               ValidateScopes = true
           });
           provider.GetService<Teacher>();
           Console.WriteLine("ok");
           Console.ReadLine();
       }
   }
}

運行報異常:
在這裏插入圖片描述
異常的原因很明顯,產生了循環依賴。

正因爲我們註冊的服務可能存在循環依賴的問題,所以容器在初次解析某個服務實例時都會對這個服務的依賴鏈進行檢查和判斷,以防止再實例化過程中出現死循環的問題。
看下面的圖所示,依賴關係雖然複雜,但是沒有產生循環依賴:
在這裏插入圖片描述
再看下面的圖所示,依賴關係雖然簡單,但是卻產生了循環依賴:
在這裏插入圖片描述

五、依賴容器對象

從上面的分析中我們知道了依賴容器有根容器和非根容器之說,那麼根容器是哪個類表示的?非根容器又是哪個類表示的?他們是同一個類嗎?
答:按照正常的理解,它們既然都是依賴容器,應該是同一個類。但事實上他們是不同的類:根容器使用類ServiceProvider(public sealed class ServiceProvider),非根容器使用類ServiceProviderEngineScope(internal class ServiceProviderEngineScope),他們都實現了IServiceProvider接口。
我們知道將服務注入到容器後,我們就可以在需要的時候向容器請求服務實例了,但是我們能不能向容器請求容器自身呢?看下面的代碼:
在這裏插入圖片描述
看上面的代碼,Student類根本不需要在構造函數中聲明具體的服務依賴,它只需要引用依賴容器本身就行了,至於其他的服務,隨時需要隨時向容器請求就行了。首先,這種方式很靈活,其次,我們儘量不要使用這種方式!!!
下面我們結合provider.Get<IServiceProvider>()provider.CreateScope().ServiceProvider來看一下根容器和非根容器的區別:
在這裏插入圖片描述
IServiceCollectionIServiceProvider的關係:
在這裏插入圖片描述
先來探討根容器的具體實現:
ServiceProvider:這個類是sealed的,所有向它發起服務解析請求的都會被轉接到其內部的IServiceProviderEngine上。IServiceProviderEngine是事實上的服務提供者,在構建ServiceProvider的時候默認創建DynamicServiceProviderEngine對象。涉及到的一些其他的IServiceProviderEngine如下:
在這裏插入圖片描述
上圖中涉及到的類全部是internal的,其中RuntimeServiceProviderEngine、ILEmitServiceProviderEngine和ExpressionsServiceProviderEngine在此次分析中並未涉及。

六、走進源碼

源碼中涉及到的接口和職責如下:

6.1 IServiceProvider

表示服務提供容器,即實現了這個接口的對象應該具有向用戶提供服務實例的能力。不過這個接口是.netcore的基礎庫裏面的,並不是包:Microsoft.Extensions.DependencyInjection包:Microsoft.Extensions.DependencyInjection.Abstractions中的。在.netcore的基礎庫中僅定義了一個GetService方法,但在包:Microsoft.Extensions.DependencyInjection.Abstractions中對這個接口做了擴展,代碼如下:

namespace System
{
    public interface IServiceProvider
    {
        object GetService(Type serviceType);
    }
}

namespace Microsoft.Extensions.DependencyInjection
{
	public static class ServiceProviderServiceExtensions
	{
		public static T GetService<T>(this IServiceProvider provider)
		{
			...
		}
		public static object GetRequiredService(this IServiceProvider provider, Type serviceType)
		{
			...
		}
		public static T GetRequiredService<T>(this IServiceProvider provider)
		{
			...
		}
		public static IEnumerable<T> GetServices<T>(this IServiceProvider provider)
		{
			...
		}
		public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType)
		{
			...
		}
		public static IServiceScope CreateScope(this IServiceProvider provider)
		{
			return provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
		}
	}
}

從上面的代碼可以看到,IServiceProvider的主要能力就是提供服務實例(GetService…),但它作爲和.netcore程序員交互的接口必須具有創建新範圍的能力,所以最後一段代碼擴展了CreateScope方法。

6.2 IServiceScope

表示一個服務範圍,在這個範圍內部存在一個IServiceProvider(服務提供容器)可以對外提供解析服務實例。它被定義在包:Microsoft.Extensions.DependencyInjection.Abstractions中。代碼如下:

namespace Microsoft.Extensions.DependencyInjection
{
	public interface IServiceScope : IDisposable
	{
		IServiceProvider ServiceProvider { get; }
	}
}

6.3 IServiceScopeFactory

表示服務範圍創建工廠,具有創建一個新範圍的能力。它被定義在包:Microsoft.Extensions.DependencyInjection.Abstractions中。代碼如下:

namespace Microsoft.Extensions.DependencyInjection
{
	public interface IServiceScopeFactory
	{
		IServiceScope CreateScope();
	}
}

6.4 IServiceProviderEngine

表示服務提供引擎,這個和IServiceProvider不同,IServiceProvider提供服務實例的能力最終是由IServiceProviderEngine提供的。它被定義在包:Microsoft.Extensions.DependencyInjection中,代碼如下:

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
	internal interface IServiceProviderEngine : IServiceProvider, IDisposable, IAsyncDisposable
	{
		IServiceScope RootScope { get; }
		void ValidateService(ServiceDescriptor descriptor);
	}
}

6.5 ServiceProvider

這是一個public sealed類,實現了IServiceProvider接口,表示根容器,最初我們使用ServiceCollection進行Build的時候就是生成它的實例。它內部封裝一個_engine(IServiceProviderEngine)對象,所有
解析服務實例的請求都轉移到這個_engine對象上。注意:它只是表示根容器,子容器並不是用它來表示的。它被定義在包:Microsoft.Extensions.DependencyInjection中,代碼如下:

namespace Microsoft.Extensions.DependencyInjection
{
	public sealed class ServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback, IAsyncDisposable
	{
		internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
		{
			...
			this._engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
		}

		public object GetService(Type serviceType)
		{
			return this._engine.GetService(serviceType);
		}
		private readonly IServiceProviderEngine _engine;
		...
	}
}

6.6 ServiceProviderEngineScope

這是一個internal類,同時實現了IServiceScope和IServiceProvider,也就是說它既可以當做範圍也可以當做服務容器。注意:所有的子容器都是由它來表示的。它的屬性ServiceProvider(IServiceProvider)指向自己,恰好說明了自身既可以是範圍也可以是服務容器。它還有一個屬性Engine(ServiceProviderEngine),指向了服務容器引擎,這裏注意:整個依賴注入框架就只有一個服務提供引擎(在由ServiceCollection進行創建根容器ServiceProvider的時候創建,一般是:DynamicServiceProviderEngine)。它被定義在包:Microsoft.Extensions.DependencyInjection中,代碼如下:

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
	internal class ServiceProviderEngineScope : IServiceScope, IDisposable, IServiceProvider, IAsyncDisposable
	{
		public ServiceProviderEngineScope(ServiceProviderEngine engine)
		{
			this.Engine = engine;
		}
		public ServiceProviderEngine Engine { get; }
		public object GetService(Type serviceType)
		{
			if (this._disposed)
			{
				ThrowHelper.ThrowObjectDisposedException();
			}
			return this.Engine.GetService(serviceType, this);
		}
		public IServiceProvider ServiceProvider
		{
			get
			{
				return this;
			}
		}
		//...
	}
}

6.7 ServiceProviderEngine

這是一個internal abstract 類,它同時實現了IServiceProviderEngine、IServiceProvider和IServiceScopeFactory接口,說明它既可以是服務提供引擎,也可以是服務容器,也可以從它創建新的範圍,不過它主要是一個服務提供引擎。上面說的 “一個依賴注入框架就只有一個服務提供引擎” ,說的就是它。也只有在它的內部具體實現了IServiceScopeFactory的CreateScope方法。它的內部還封裝着Root和RootScope屬性(Root=RootScope,ServiceProviderEngineScope),這兩個屬性引用的同一個對象:根容器範圍(ServiceProviderEngineScope),這個根容器範圍是在創建ServiceProviderEngine的時候一起創建的。它的子類DynamicServiceProviderEngine應該是我們使用的使用的具體對象了,涉及代碼如下:

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
	internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceProvider, IDisposable, IAsyncDisposable, IServiceScopeFactory
	{
		protected ServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngineCallback callback)
		{
		    //...
			this.Root = new ServiceProviderEngineScope(this);
		}
		public ServiceProviderEngineScope Root { get; }
		public IServiceScope RootScope
		{
			get
			{
				return this.Root;
			}
		}
		public object GetService(Type serviceType)
		{
			return this.GetService(serviceType, this.Root);
		}
		internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
		{
			//...
		}
		public IServiceScope CreateScope()
		{
			if (this._disposed)
			{
				ThrowHelper.ThrowObjectDisposedException();
			}
			return new ServiceProviderEngineScope(this);
		}
		//...
	}
}
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
	internal abstract class CompiledServiceProviderEngine : ServiceProviderEngine
	{
		public CompiledServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngineCallback callback) : base(serviceDescriptors, callback)
		{
			//...
		}
		//...
	}
}
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
	internal class DynamicServiceProviderEngine : CompiledServiceProviderEngine
	{
		public DynamicServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngineCallback callback) : base(serviceDescriptors, callback)
		{
		}
		//...
	}
}

6.8 相關接口和類總結

首先看下面的圖:
在這裏插入圖片描述
上圖中顯示了依賴注入涉及到的主要接口和類的相互之間關係。下面來看一下,服務容器、服務提供引擎和服務範圍之間的關係:
在這裏插入圖片描述

6.9 重點問題

  • 依賴注入框架就只有一個服務提供引擎(一般是:DynamicServiceProviderEngine),這是怎麼做到的?

    首先,由ServiceCollection創建ServiceProvider的時候就創建了服務提供引擎,並將它儲存在了ServiceProvider中,之後由ServiceProvider創建新的範圍的時候(或者是由子範圍創建新範圍的時候),其實就是調用服務提供引擎的CreateScope方法,而服務提供引擎的CreateScope方法執行的時候將自身傳遞給了新創建的範圍(new ServiceProviderEngineScope(this)),所以整個依賴框架就只有一個服務提供引擎。

七、注入泛型服務

依賴框架在解析泛型服務的時候相當靈活,我們先定義下面的泛型類:

public class Teacher
{
    public Teacher()
    {
        Console.WriteLine("Teacher ctor...");
    }
}
public class Book
{
    public Book()
    {
        Console.WriteLine("Book ctor...");
    }
}
public class Student<T>
{
    public Student(T t)
    {
        Console.WriteLine("Student ctor...");
    }
}

依賴注入中的簡單泛型:
我們可以在依賴注入的時候指定泛型的具體類型,看下面的代碼:

class Program
{
    public static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddSingleton<Teacher>();
        services.AddSingleton<Book>();
        services.AddSingleton<Student<Book>>();
        services.AddSingleton<Student<Teacher>>();
        var provider = services.BuildServiceProvider();

        var serv = provider.GetService<Student<Book>>();
        var serv2 = provider.GetService<Student<Teacher>>();
        Console.WriteLine("ok");
        Console.ReadLine();
    }
}

運行效果:
在這裏插入圖片描述
這個泛型應用起來還是比較簡單的,但是不夠靈活。比如說我只想將Student注入進去,至於具體的泛型類型我想解析的時候再指定,那怎麼辦呢?看下面的代碼:

class Program
 {
     public static void Main(string[] args)
     {
         var services = new ServiceCollection();
         services.AddSingleton<Teacher>();
         services.AddSingleton<Book>();
         services.AddSingleton(typeof(Student<>));
         var provider = services.BuildServiceProvider();

         var serv = provider.GetService<Student<Book>>();
         var serv2 = provider.GetService<Student<Teacher>>();
         Console.WriteLine("ok");
         Console.ReadLine();
     }
 }

運行效果如下:
在這裏插入圖片描述
這樣應用起來就相當靈活了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章