.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()和provider.CreateScope().ServiceProvider来看一下根容器和非根容器的区别:

请添加图片描述

IServiceCollection和IServiceProvider的关系:

请添加图片描述

先来探讨根容器的具体实现:
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();
     }
 }

运行效果如下:

请添加图片描述
这样应用起来就相当灵活了。

原文链接:https://blog.csdn.net/u010476739/article/details/105999059

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值