.NET CORE依赖注入

ASP.NET Core 应用在启动以及后续针对请求的处理过程中会依赖各种组件提供服务。为了便于定制,这些组件一般会以接口的形式进行标准化,我们将这些标准化的组件统一称为服务(Service)。整个ASP.NET Core框架建立在一个底层的依赖注入框架之上,它使用依赖注入容器提供所需的服务对象。

控制反转

流程控制的反转

IoC的英文全称是Inverse of Control,可译为控制反转或者控制倒置。控制反转和控制倒置体现的都是控制权的转移

IoC涉及的控制可以理解为“针对流程的控制”,IoC是设计框架所采用的一种基本思想,所谓的控制反转就是将应用对流程的控制转移到框架之中。

类库和框架

通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照好莱坞法则实现应用程序的代码与框架之间的交互。

类库(Library)和框架(Framework)的不同之处在于:前者往往只是提供实现某种单一功能的 API;而后者则针对一个目标任务对这些单一功能进行编排,以形成一个完整的流程,并利用一个引擎来驱动这个流

程自动执行。


一般来说,框架会以相应的形式提供一系列的扩展点,应用程序通过注册扩展的方式实现对流程某个环节的定制。

IOC思想

IoC几乎是所有框架均具有的一个固有属性,从这个意义上讲,IoC 框架其实是一种错误的说法,可以说世界上本没有IoC框架,也可以说所有的框架都是IoC框架。

我们可以采用若干设计模式以不同的方式实现 IoC,如模板方法、工厂方法和抽象工厂

依赖注入是IOC思想的实现方式

依赖注入

由容器提供对象

依赖注入是一种“对象提供型”的设计模式,可以将提供的对象统称为“服务”“服务对象”“服务实例”。在一个采用依赖注入的应用中,我们定义某个类型时,只需要直接将它依赖的服务采用相应的方式注入进来即可


在应用启动时,我们会对所需的服务进行全局注册。一般来说,服务大都是针对实现的接口或者继承的抽象类进行注册的,服务注册信息会在后续消费过程中帮助我们提供对应的服务实例。


被框架用来提供服务的容器称为依赖注入容器


由于服务注册最终决定了依赖注入容器根据指定的服务类型会提供一个什么样的服务实例,所以我们可以通过修改服务注册的方式来实现对框架的定制。

三种依赖注入方式

类与类之间的耦合可以通过对依赖进行抽象的方式来降低或者解除。

作为服务对象提供者的依赖注入容器,它会根据依赖链提供所有的依赖服务实例。

构造器注入【最理想方式】

        构造器注入就是在构造函数中借助参数将依赖的对象注入由它创建的对象之中。

        构造器注入在对构造函数的选择上可能有不同的策略,例如可以在目标构造器函数上标注一个InjectionAttribute特性。


 

属性注入

        如果依赖直接体现为类的某个属性,并且该属性不是只读的,就可以让依赖注入容器在对象创建之后自动对其进行赋值,进而达到依赖注入的目的。


 

方法注入

        体现依赖关系的字段或者属性可以通过方法的形式初始化。


ASP.NET Core更加方便的注入方式

        

        ASP.NET Core在启动的时候会调用注册的 Startup 对象来完成中间件的注册,而定义 Startup 类型的时候不需要让它实现某个接口,所以用于注册中间件的 Configure方法没有一个固定的声明,因此可以将某接口作为参数传入Configure中。


        ASP.NET Core 框架下的中间件类型同样不需要实现某个预定义的接口,用于处理请求的InvokeAsync方法或者Invoke方法同样可以注入任意的依赖服务。

Service Locator模式

Service Locator模式同样具有一个通过服务注册创建的全局的容器来提供所需的服务实例,

该容器被称为Service Locator。

依赖注入和Service Locator之间的差异主要体现在哪些方面?

依赖注入容器的使用者应该是框架而不是应用程序。Service Locator模式显然不是这样,而是应用程序在利用它来提供所需的服务实例,所以它的使用者是应用程序。

从另外一个角度区分两者之间的差别。由于依赖服务是以“注入”的方式来提供的,所以采用依赖注入模式的应用可以看作将服务推送到依赖注入容器,Service Locator模式下的应用则是利用 Service Locator 拉取所需的服务,这一“推”一“拉”也准确地体现了两者之间的差异。

依赖注入和service locator模式的本质区别:

当前服务针对另一个服务的依赖与针对依赖注入容器或者 Service Locator 的依赖具有本质上的不同。前者是一种基于类型的依赖,不论是基于服务的接口还是实现类型,这是一种基于“契约”的依赖。这种依赖不仅是明确的,也是有保障的。但是依赖注入容器或者 Service Locator 本质上是一个黑盒,它能够提供所需服务的前提是相应的服务注册已经预先添加到容器之中,但是这种依赖不仅是模糊的也是不可靠的。

依赖注入容器编程体验

容器Cat之间的层次关系

以上反映的是容器的属性层次化结构,反映的是逻辑结构,实际上,每个Cat对象只会按照图所示的方式引用整棵树的根。 

预定义的生命周期

依赖注入框架利用 ServiceLifetime 来表示 Singleton、Scoped和Transient这3种生命周期模式

Transient 代表容器针对每次服务请求都会创建一个新的服务实例;

Self 将提供服务实例保存在当前容器中,它代表针对某个容器范围内的单例模式;

Root 将每个容器提供的服务实例统一存放到根容器中,所以该模式能够在多个“同根”容器范围内确保提供的服务是单例的。

	namespace App
	{
	    public enum Lifetime
	    {
	        // 将每个容器提供的服务实例统一存放到根容器中,
	        // 所以该模式能够在多个“同根”容器范围内确保提供的服务是单例的
	        Root,
	        //将提供服务实例保存在当前容器中,它代表针对某个容器范围内的单例模式
	        Self,
	        //代表容器针对每次服务请求都会创建一个新的服务实例
	        Transient
	    }
}

服务的注册与消费

依赖注入框架主要涉及两个NuGet包,我们在编程过程中频繁使用的一些接口和基础数据类型都定义在NuGet包“Microsoft.Extensions.DependencyInjection.Abstractions”中,而依赖注入的具体实现则由NuGet包“Microsoft.Extensions.DependencyInjection”来承载

IServiceCollection接口

服务注册被保存在IServiceCollection接口表示的集合中,应用启动时,针对服务的注册本质上就是创建相应的ServiceDescriptor对象并将其添加到指定 IServiceCollection 对象中的过程。

 

由这个集合创建的依赖注入容器体现为一个IServiceProvider对象。由于 IServiceProvider 对象总是利用指定的服务类型来提供对应的服务实例,所以服务总是基于类型进行注册。


ServiceCollection对象(它是对IServiceCollection接口的默认实现)

 

完成服务注册之后,我们调用 IServiceCollection 接口的BuildServiceProvider 扩展方法创建出代表依赖注入容器的 IServiceProvider 对象,并调用该对象的GetService<T>方法来提供相应的服务实例

 依赖注入框架对该方法采用了“后来居上”的策略,也就是说,依赖注入容器总是采用最近添加的服务注册来创建服务实例。如果调用 GetServices<TService>扩展方法,该方法将利用指定服务类型的所有服务注册来提供一组服务实例。 

 

生命周期

         Singleton 服务实例保存在作为根容器的 IServiceProvider 对象上,所以它能够在多个同根IServiceProvider对象之间提供真正的单例保证。

        Scoped服务实例被保存在当前 IServiceProvider对象上,所以它只能在当前范围内保证提供的实例是单例的。

        没有实现 IDisposable 接口的Transient服务则采用“即用即建,用后即弃”的策略。

作为依赖注入容器的 IServiceProvider 对象不仅可以提供所需的服务实例,还可以管理这些服务实例的生命周期。如果某个服务类型实现了 IDisposable 接口,就意味着当生命周期完结的时候需要通过调用 Dispose 方法执行一些资源释放操作,这些操作同样由提供该服务实例的IServiceProvider 对象来驱动执行。依赖注入框架针对提供服务实例的释放策略取决于对应的服务注册所采用的生命周期模式,具体的策略如下。

        ● Transient和 Scoped:所有实现了 IDisposable接口的服务实例会被当前 IServiceProvider对象保存起来,当 IServiceProvider对象的 Dispose方法被调用的时候,这些服务实例的Dispose方法会随之被调用。

        ● Singleton:由于服务实例保存在作为根容器的 IServiceProvider对象上,所以只有当后者的Dispose方法被调用的时候,这些服务实例的Dispose方法才会随之被调用

针对服务注册的验证

如果某个 Singleton 服务依赖另一个Scoped服务,那么Scoped服务实例将被一个Singleton服务实例所引用,也就意味着Scoped服务实例成了一个Singleton服务实例。试想一下,如果该Scoped服务实例引用的资源(如数据库连接)需要被及时释放,这可能会造成难以估量的后果。

如果希望 IServiceProvider 对象在提供服务的过程中可以对服务范围做有效性检验,只需要在调用IServiceCollection接口的BuildServiceProvider扩展方法时,将一个布尔类型的True值作为参数即可。

一旦开启了针对服务范围的验证,IServiceProvider 对象不可能提供以单例形式存在的Scoped服务 

配置选项类型ServiceProviderOptions提供了两个属性:ValidateScopes属性表示是否需要开启针对服务范围的检验;ValidateOnBuild 属性表示是否需要预先检验作为服务注册的每个 ServiceDescriptor 对象能否提供对应的服务实例。

ServiceDescriptor

ServiceDescriptor是对某个服务注册项的描述,作为依赖注入容器的IServiceProvider对象正是利用该对象提供的描述信息才得以提供我们需要的服务实例

ServiceDescriptor的其他3个属性体现了服务实例的3种提供方式,并且分别对应3个构造函数。

第一个构造函数:服务实例通过调用构造函数获得

第二个构造函数:服务实例通过工厂来提供

第三个构造函数:直接指定一个现成的对象(对应的属性为 ImplementationInstance),那么该对象就是最终提供的服务实例


如果采用现成的服务实例来创建ServiceDescriptor对象,对应的服务注册就会采用Singleton生命周期模式。对于通过其他两个构造函数创建的 ServiceDescriptor 对象来说,需要显式指定采用的生命周期模式。 

服务的消费

包含服务注册信息的 IServiceCollection 集合最终被用来创建作为依赖注入容器的IServiceProvider 对象。

IServiceProvider

 

GetService<T>方法以泛型参数的形式指定了服务类型,返回的服务实例也会做对应的类型转换。如果指定服务类型的服务注册不存在,GetService 方法会返回 Null,如果调用GetRequiredService方法或者 GetRequiredService<T>方法则会抛出一个 InvalidOperationException类型的异常 

服务实例的创建

ServiceDescriptor对象具有3个不同的构造函数,分别对应服务实例最初的3种提供方式,如果提供的是服务的实现类型,最终提供的服务实例将通过调用该类型的某个构造函数来创建,那么构造函数是通过什么策略被选择出来的?


        IServiceProvider对象能够提供构造函数的所有参数        

        每个候选构造函数的参数类型集合都是这个构造函数参数类型集合的子集,即一个构造函数的参数类型集合能够成为所有有效构造函数参数类型集合的超集

生命周期

生命周期决定了 IServiceProvider 对象采用什么样的方式提供和释放服务实例。

服务范围:

①Singleton

        IServiceProvider对象创建的服务实例保存在作为根容器的 IServiceProvider对象中,所以多个同根的IServiceProvider对象提供的针对同一类型的服务实例都是同一个对象

②Transient
       针对每次服务提供请求,IServiceProvider 对象总是创建一个新的服务实例

③Scoped

         IServiceProvider对象创建的服务实例由自己保存,所以同一个 IServiceProvider对象提供的针对同一类型的服务实例均是同一个对象

         Scoped指的是由IServiceScope接口表示的服务范围,该范围由 IServiceScopeFactory接口表示的“服务范围工厂”来创建。

 

上图所示的树形层次结构只是一种逻辑结构,从对象引用层面来看,通过某个 IServiceScope 封装的 IServiceProvider 对象不需要知道自己的“父亲”是谁,它只关心作为根节点的IServiceProvider对象在哪里 

上图从物理层面揭示了 IServiceScope/IServiceProvider对象之间的关系,任何一个IServiceProvider对象都具有针对根容器的引用 


IServiceProvider对象针对服务实例采用的回收释放策略取决于采用的生命周期模式,具体策略主要体现为如下两点:

        ● Singleton:提供 Disposable服务实例保存在作为根容器的 IServiceProvider对象上,只有在这个IServiceProvider对象被释放的时候,这些Disposable服务实例才能被释放。

        ● Scoped和 Transient:IServiceProvider对象会保存由它提供的Disposable服务实例,当自己被释放的时候,这些Disposable服务实例就会被释放。


ASP.NET Core应用


         依赖注入框架所谓的服务范围在 ASP.NET Core 应用中具有明确的边界,指的是针对每个HTTP请求的上下文,也就是说,服务范围的生命周期与每个请求的上下文绑定在一起。

        ASP.NET Core 应用中用于提供服务实例的 IServiceProvider 对象分为两种类型:一种是作为根容器并与应用具有相同生命周期的 IServiceProvider 对象,一般称为 ApplicationServices;另一种是根据请求及时创建和释放的IServiceProvider对象,一般称为RequestServices。在 ASP.NET Core 应用初始化过程(即请求管道构建过程)中使用的服务实例都是由ApplicationServices提供的。

 

实现概览

ServiceProviderEngine

         表示提供服务实例的提供引擎,容器提供的服务实例最终是通过该引擎提供的,在一个应用范围内只存在一个全局唯一的ServiceProviderEngine对象 

ServiceProviderEngineScope

        代表服务范围,它利用对提供服务实例的缓存来实现对生命周期的控制


ServiceProviderEngine是一个抽象类,.NET Core依赖注入框架提供了如下4个具体的实现类型,默认使用的是DynamicServiceProviderEngine

ServiceProvider

         调用IServiceCollection集合的BuildServiceProvider扩展方法创建的是一个ServiceProvider对象。


                ServiceProviderEngine和ServiceProviderEngineScope之间的关系

 

在利用 IServiceCollection 集合创建 ServiceProvider 对象的时候,提供的服务注册将用来创建一个具体的ServiceProviderEngine对象。该ServiceProviderEngine对象的RootScope就是它创建的一个ServiceProviderEngineScope对象,子容器提供的Singleton服务实例由它维护。

依赖注入框架具有如下几方面特性

ServiceProviderEngine 的唯一性:

        整个服务提供体系只存在一个 ServiceProviderEngine对象

ServiceProviderEngine与IServiceFactory的同一性:

        唯一存在的ServiceProviderEngine会作为创建服务范围的IServiceFactory工厂

ServiceProviderEngineScope 和 IServiceProvider 的同一性:

        表示服务范围的ServiceProviderEngineScope也作为服务提供者的依赖注入容器。

扩展

适配

.NET Core 具有一个承载(Hosting)系统,该系统承载需要在后台运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已。承载系统总是采用依赖注入的方式消费它在服务承载过程中所需的服务


对于承载系统来说,原始的服务注册总是体现为一个 IServiceCollection集合,最终的依赖注入容器则体现为一个 IServiceProvider对象,如果要将第三方依赖注入框架整合进来,就需要利用它们解决从 IServiceCollection 集合到IServiceProvider对象的适配问题。


具体来说,我们可以在 IServiceCollection集合和 IServiceProvider对象之间设置一个针对某个第三方依赖注入框架的 ContainerBuilder对象先利用包含原始服务注册的 IServiceCollection集合创建一个ContainerBuilder对象,再利用该对象构建作为依赖注入容器的IServiceProvider对象

 

IServiceProviderFactory<TContainerBuilder>

两种转换是利用一个 IServiceProviderFactory<TContainerBuilder>对象完成

IServiceProviderFactory<TContainerBuilder>接口定义了两个方法:CreateBuilder 方法利用指定的 IServiceCollection 集合创建对应的 ContainerBuilder 对象;CreateServiceProvider 方法则进一步利用这个 ContainerBuilder 对象创建作为依赖注入容器的IServiceProvider对象。         

整合第三方依赖注入框架示例

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值