[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
《服务注册》、《服务消费》和《生命周期》主要从实现原理的角度对.NET Core的依赖注入框架进行了介绍,接下来更进一步,看看该框架的总体设计和实现。在过去的多个版本更迭过程中,依赖注入框架的底层实现一直都在发生改变,加上底层的涉及的大都是内容接口和类型,所以我们不打算涉及太过细节的层面。
一、ServiceProviderEngine & ServiceProviderEngineScope
对于依赖注入的底层设计和实现来说,ServiceProviderEngine和ServiceProviderEngineScope是两个最为核心的类型。顾名思义,ServiceProviderEngine表示提供服务实例的提供引擎,服务实例最终是通过该引擎提供的,在一个应用范围内只存在一个全局唯一的ServiceProviderEngine对象。ServiceProviderEngineScope代表服务范围,它利用对提供服务实例的缓存实现对生命周期的控制。ServiceProviderEngine实现了接口IServiceProviderEngine,从如下的代码片段可以看出,一个ServiceProviderEngine对象同时也是一个IServiceProvider对象,还是一个IServiceScopeFactory对象。
internal interface IServiceProviderEngine : IServiceProvider, IDisposable, IAsyncDisposable
{
void ValidateService(ServiceDescriptor descriptor);
IServiceScope RootScope { get; }
} internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceScopeFactory
{
public IServiceScope RootScope { get; }
public IServiceScope CreateScope();
...
}
ServiceProviderEngine的RootScope属性返回的IServiceScope对象是为根容器提供的服务范围。作为一个IServiceScopeFactory对象,ServiceProviderEngine的CreateScope会创建一个新的服务范围,这两种服务范围都通过一个ServiceProviderEngineScope对象来表示。
internal class ServiceProviderEngineScope : IServiceScope, IDisposable, IServiceProvider, IAsyncDisposable
{
public ServiceProviderEngine Engine { get; }
public IServiceProvider ServiceProvider { get; }
public object GetService(Type serviceType);
}
如上面的代码片段所示,一个ServiceProviderEngineScope对象不仅是一个IServiceScope对象,还是一个IServiceProvider对象。在《生命周期》中,我们说表示服务范围的IServiceScope对象是对一个表示依赖注入容器的IServiceProvider对象的封装,实际上两者合并为同一个ServiceProviderEngineScope对象,一个ServiceProviderEngineScope对象的ServiceProvider属性返回的就是它自己。换句话说,我们所谓的子容器和它所在的服务范围引用的都是同一个ServiceProviderEngineScope对象。
下图进一步揭示了ServiceProviderEngine和ServiceProviderEngineScope之间的关系。对于一个通过调用ServiceProviderEngine对象的CreateScope创建的ServiceProviderEngineScope来说,由于它同时也是一个IServiceProvider对象,如果我们调用它的GetService<IServiceProvider>方法,该方法同样返回它自己。如果我们调用它的GetService<IServiceScopeFactory>方法,它返回创建它的ServiceProviderEngine对象,也就是该方法和Engine属性返回同一个对象。
依赖注入框架提供的服务实例最终是通过ServiceProviderEngine对象提供的。从上面给出的代码片段可以看出,ServiceProviderEngine是一个抽象类,.NET Core依赖注入框架提供了如下四个具体的实现类型,默认使用的是DynamicServiceProviderEngine。
- RuntimeServiceProviderEngine:采用反射的方式提供服务实例;
- ILEmitServiceProviderEngine:采用IL Emit的方式提供服务实例;
- ExpressionsServiceProviderEngine:采用表达式树的方式提供服务实例;
- DynamicServiceProviderEngine:根据请求并发数量动态决定最终的服务实例提供方案(反射和者IL Emit或者反射与表达式树,是否选择IL Emit取决于当前运行时是否支持Reflection Emit)。
4.4.2. ServiceProvider
调用IServiceCollection集合的扩展方法BuildServiceProvider创建的是一个ServiceProvider对象。作为根容器的ServiceProvider对象,和前面介绍的ServiceProviderEngine和ServiceProviderEngineScope对象,一起构建了整个依赖注入框架的设计蓝图。
在利用IServiceCollection集合创建ServiceProvider对象的时候,提供的服务注册将用来创建一个具体的ServiceProviderEngine对象。该ServiceProviderEngine对象的RootScope就是它创建的一个ServiceProviderEngineScope对象,子容器提供的Singleton服务实例由它维护。如果调用ServiceProvider对象的GetService<IServiceProvider>方法,返回的其实不是它自己,而是作为RootScope的ServiceProviderEngineScope对象(调用ServiceProviderEngineScope对象的GetService<IServiceProvider>方法返回的是它自己)。
ServiceProvider和ServiceProviderEngineScope都实现了IServiceProvider接口,如果我们调用了它们的GetService<IServiceScopeFactory>方法,返回的都是同一个ServiceProviderEngine对象。这一个特性决定了调用它们的CreateScope扩展方法都会创建一个新的ServiceProviderEngineScope对象作为子容器。综上所述,我们针对依赖注入框架总结出如下的特性:
- ServiceProviderEngine的唯一性:整个服务提供体系只存在一个唯一的ServiceProviderEngine对象。
- ServiceProviderEngine与IServiceFactory的同一性:唯一存在的ServiceProviderEngine会作为创建服务范围的IServiceFactory工厂。
- ServiceProviderEngineScope和IServiceProvider的同一性:表示服务范围的ServiceProviderEngineScope同时也是作为服务提供者的依赖注入容器。
为了印证我们总结出来的特性,我们编写的测试代码。由于设计的ServiceProviderEngine和ServiceProviderEngineScope都是内部类型,我们只能采用反射的方式得到它们的属性或者字段成员。上面总结的这些特征体现在如下几组调试断言中。
class Program
{
static void Main()
{
var (engineType, engineScopeType) = ResolveTypes();
var root = new ServiceCollection().BuildServiceProvider();
var child1 = root.CreateScope().ServiceProvider;
var child2 = root.CreateScope().ServiceProvider; var engine = GetEngine(root);
var rootScope = GetRootScope(engine, engineType); //ServiceProviderEngine的唯一性
Debug.Assert(ReferenceEquals(GetEngine(rootScope, engineScopeType), engine));
Debug.Assert(ReferenceEquals(GetEngine(child1, engineScopeType), engine));
Debug.Assert(ReferenceEquals(GetEngine(child2, engineScopeType), engine)); //ServiceProviderEngine和IServiceScopeFactory的同一性
Debug.Assert(ReferenceEquals(root.GetRequiredService<IServiceScopeFactory>(), engine));
Debug.Assert(ReferenceEquals(child1.GetRequiredService<IServiceScopeFactory>(), engine));
Debug.Assert(ReferenceEquals(child2.GetRequiredService<IServiceScopeFactory>(), engine)); //ServiceProviderEngineScope提供的IServiceProvider是它自己
//ServiceProvider提供的IServiceProvider是RootScope
Debug.Assert(ReferenceEquals(root.GetRequiredService<IServiceProvider>(), rootScope));
Debug.Assert(ReferenceEquals(child1.GetRequiredService<IServiceProvider>(), child1));
Debug.Assert(ReferenceEquals(child2.GetRequiredService<IServiceProvider>(), child2)); //ServiceProviderEngineScope和IServiceProvider的同一性
Debug.Assert(ReferenceEquals((rootScope).ServiceProvider, rootScope));
Debug.Assert(ReferenceEquals(((IServiceScope)child1).ServiceProvider, child1));
Debug.Assert(ReferenceEquals(((IServiceScope)child2).ServiceProvider, child2));
} static (Type Engine, Type EngineScope) ResolveTypes()
{
var assembly = typeof(ServiceProvider).Assembly;
var engine = assembly.GetTypes().Single(it => it.Name == "IServiceProviderEngine");
var engineScope = assembly.GetTypes().Single(it => it.Name == "ServiceProviderEngineScope");
return (engine, engineScope);
} static object GetEngine(ServiceProvider serviceProvider)
{
var field = typeof(ServiceProvider).GetField("_engine", BindingFlags.Instance | BindingFlags.NonPublic);
return field.GetValue(serviceProvider);
} static object GetEngine(object enginScope, Type engineScopeType)
{
var property = engineScopeType.GetProperty("Engine", BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(enginScope);
} static IServiceScope GetRootScope(object engine, Type engineType)
{
var property = engineType.GetProperty("RootScope", BindingFlags.Instance | BindingFlags.Public);
return (IServiceScope)property.GetValue(engine);
}
}
三、注入IServiceProvider对象
在《依赖注入模式》中,我们从“Service Locator”设计模式是反模式的角度说明了为什么不推荐在服务中注入IServiceProvider对象。不过反模式并不就等于是完全不能用的模式,有些情况下直接在服务构造函数中注入作为依赖注入容器的IServiceProvider对象可能是最快捷省事的解决方案。对于IServiceProvider对象的注入,有个细节大家可能忽略或者误解。
读者朋友们可以试着思考这么一个问题:如果我们在某个服务中注入了IServiceProvider对象,当我们利用某个IServiceProvider对象来提供该服务实例的时候,注入的IServiceProvider对象是它自己吗?以如下所示的代码片段为例,我们定义了两个在构造函数中注入了IServiceProvider对象的服务类型SingletonService和ScopedService,并按照命名所示的生命周期进行了注册。
class Program
{
static void Main()
{
var serviceProvider = new ServiceCollection()
.AddSingleton<SingletonService>()
.AddScoped<ScopedService>()
.BuildServiceProvider();
using (var scope = serviceProvider.CreateScope())
{
var child = scope.ServiceProvider;
var singletonService = child.GetRequiredService<SingletonService>();
var scopedService = child.GetRequiredService<ScopedService>(); Debug.Assert(ReferenceEquals(child, scopedService.RequestServices));
Debug.Assert(ReferenceEquals(rootScope, singletonService.ApplicationServices));
}
} public class SingletonService
{
public IServiceProvider ApplicationServices { get; }
public SingletonService(IServiceProvider serviceProvider) => ApplicationServices = serviceProvider;
} public class ScopedService
{
public IServiceProvider RequestServices { get; }
public ScopedService(IServiceProvider serviceProvider) => RequestServices = serviceProvider;
}
}
我们最终利用一个作为子容器的IServiceProvider对象(ServiceProviderEngineScope对象)来提供这来个服务类型的实例,并通过调试断言确定注入的IServiceProvider对象是否就是作为当前依赖注入容器的ServiceProviderEngineScope对象。如果在Debug模式下运行上述的测试代码,我们会发现第一个断言是成立的,第二个则不成立。
再次回到两个服务类型的定义,SingletonService和ScopedService中通过注入IServiceProvider对象初始化的属性分别被命名为ApplicationServices和RequestServices,意味着它们希望注入的分别是针对当前应用程序的根容器和针对请求的子容器。当我们利用针对请求的子容器来提供针对这两个类型的服务实例时,如果注入的当前子容器的话,就与ApplicationServices的意图不符。所以在提供服务实例的注入的IServiceProvider对象取决于采用的生命周期模式,具体策略为:
- Singleton:注入的是ServiceProviderEngine的RootScope属性表示的ServiceProviderEngineScope对象。
- Scoped和Transient:如果当前IServiceProvider对象类型为ServiceProviderEngineScope,注入的就是它自己,如果是一个ServiceProvider对象,注入的还是ServiceProviderEngine的RootScope属性表示的ServiceProviderEngineScope对象。
基于生命周期模式注入IServiceProvider对象的策略可以通过如下这个测试程序来验证。最后还有一点需要补充一下:我们将调用IServiceCollection集合的BuildServiceProvider扩展方法创建的ServiceProvider对象作为根容器,它对应的ServiceProviderEngine对象的RootScope属性返回作为根服务范围的ServiceProviderEngineScope对象,ServiceProvider、ServiceProviderEngine和ServiceProviderEngineScope这三个类型全部实现了IServiceProvider接口,这三个对象都可以视为根容器。
class Program
{
static void Main()
{
var serviceProvider = new ServiceCollection()
.AddSingleton<SingletonService>()
.AddScoped<ScopedService>()
.BuildServiceProvider();
var rootScope = serviceProvider.GetService<IServiceProvider>();
using (var scope = serviceProvider.CreateScope())
{
var child = scope.ServiceProvider;
var singletonService = child.GetRequiredService<SingletonService>();
var scopedService = child.GetRequiredService<ScopedService>(); Debug.Assert(ReferenceEquals(child, child.GetRequiredService<IServiceProvider>()));
Debug.Assert(ReferenceEquals(child, scopedService.RequestServices));
Debug.Assert(ReferenceEquals(rootScope, singletonService.ApplicationServices));
}
}
}
[ASP.NET Core 3框架揭秘] 依赖注入[1]:控制反转
[ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
[ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
[ASP.NET Core 3框架揭秘] 依赖注入[4]:一个迷你版DI框架
[ASP.NET Core 3框架揭秘] 依赖注入[5]:利用容器提供服务
[ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
[ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
[ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述的更多相关文章
- [ASP.NET Core 3框架揭秘] 依赖注入:控制反转
ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务
毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
生命周期决定了IServiceProvider对象采用怎样的方式提供和释放服务实例.虽然不同版本的依赖注入框架针对服务实例的生命周期管理采用了不同的实现,但总的来说原理还是类似的.在我们提供的依赖注入 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
.NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已.承载系统总是采用依赖注入的方式来消费它在服务承载过 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
包含服务注册信息的IServiceCollection集合最终被用来创建作为依赖注入容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IService ...
- [ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
通过<利用容器提供服务>我们知道作为依赖注入容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架
在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照"好莱坞法则"实现应用程序的代码与框架之间的交互.我们可以采用若干设计模式 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
正如我们在<依赖注入:控制反转>提到过的,很多人将IoC理解为一种"面向对象的设计模式",实际上IoC不仅与面向对象没有必然的联系,它自身甚至算不上是一种设计模式.一般 ...
随机推荐
- H3C交换机、路由器 ssh登录配置
VLAN 10 创建vlan并配好ip inter vlan 10 ip add 20.1.1.1 24 qu ip route-static 0.0.0.0 0 20.1.1.254 写好静 ...
- Hadoop简述
Haddop是什么? Hadoop是一个由Apache基金会所开发的分布式系统基础架构 主要解决,海量数据的存储和海量数据的分析计算问题. Hadoop三大发行版本 Apache版本最原始(最基础)的 ...
- (五)OpenStack---M版---双节点搭建---Nova安装和配置
↓↓↓↓↓↓↓↓视频已上线B站↓↓↓↓↓↓↓↓ >>>>>>传送门 1.创建nova 和 nova_api数据库 2.获得 admin 凭证来获取只有管理员能执行的 ...
- applicationContext-dao.xml 配置错误
https://www.captainbed.net/ 配置文件报错: 不允许有匹配 "[xX][mM][lL]" 的处理指令目标. 错误原因: 由于大部分都是搬砖,所以格式没注意 ...
- Sql server中用现有表中的数据创建Sql的Insert插入语句
之前,在Codeproject发表过一篇关于用现有表中数据创建Insert的Sql语句的存储过程,今天将其搬到这里来,注意本存储过程仅适用于SQL SERVER. 介绍 一些时候,你想导出一些现有表中 ...
- win10在python3.6里安装pycrypto-2.6.1
简单的一步搞定 下载pycrypto-2.6.1-cp36-cp36m-win_amd64.whl文件,然后pip install即可 链接: https://pan.baidu.com/s/1Awl ...
- Bootstrap 元素居中设置
一.Bootstrap水平居中 1. 文本:class ="text-center" 2. 图片居中:class = "center-block" 3.其他元素 ...
- JavaEE基础(01):Servlet实现方式,生命周期执行过程
本文源码:GitHub·点这里 || GitEE·点这里 一.Servlet简介 Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容.使用S ...
- Python中 * 与 **, *args 与 **kwargs的用法
* 用于传递位置参数(positional argument) ** 用于传递关键字参数(keyword argument) 首先,先通过一个简单的例子来介绍 * 的用法: def add_funct ...
- variable fonts - 更小更灵活的字体
原文链接 variable fonts(下文中vf为缩写)是数字时代制作的字体技术,用更小的文件大小在web上提供更丰富的排版,但是一项新的技术往往伴随着新的挑战和复杂未知的情况.不过,我们要拥抱技术 ...