各层作用_终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的

一:背景

1. 讲故事

前几天有位朋友让我有时间分析一下 aspnetcore 中为什么向 ServiceCollection 中注入的 Class 可以做到 Singleton,Transient,Scoped,挺有意思,这篇就来聊一聊这一话题,自从 core 中有了 ServiceCollection, 再加上流行的 DDD 模式,相信很多朋友的项目中很少能看到 new 了,好歹 spring 十几年前就是这么干的。

二:Singleton,Transient,Scoped 基本用法

分析源码之前,我觉得有必要先介绍一下它们的玩法,为方便演示,我这里就新建一个 webapi 项目,定义一个 interface 和 concrete ,代码如下:

    public class OrderService : IOrderService
{
private string guid;

public OrderService(){
guid = $"时间:{DateTime.Now}, guid={ Guid.NewGuid()}";
}

public override string ToString(){
return guid;
}
}

public interface IOrderService
{
}

1. AddSingleton

正如名字所示它可以在你的进程中保持着一个实例,也就是说仅有一次实例化,不信的话代码演示一下哈。


public class Startup
{
public void ConfigureServices(IServiceCollection services){
services.AddControllers();

services.AddSingleton();
}
}
[ApiController]
[Route("[controller]")]public class WeatherForecastController : ControllerBase
{
IOrderService orderService1;
IOrderService orderService2;public WeatherForecastController(IOrderService orderService1, IOrderService orderService2){this.orderService1 = orderService1;this.orderService2 = orderService2;
}
[HttpGet]public string Get(){
Debug.WriteLine($"{this.orderService1}\r\n{this.orderService2} \r\n ------");return "helloworld";
}
}

接着运行起来多次刷新页面,如下图:

f42a5824f3b1207ebf1c7beb9b1e5853.png

可以看到,不管你怎么刷新页面,guid都是一样,说明确实是单例的。

2. AddScoped

正从名字所述:Scope 就是一个作用域,那在 webapi 或者 mvc 中作用域是多大呢?对的,就是一个请求,当然请求会穿透 Presentation, Application, Repository 等等各层,在穿层的过程中肯定会有同一个类的多次注入,那这些多次注入在这个作用域下维持的就是单例,如下代码所示:


public void ConfigureServices(IServiceCollection services){
services.AddControllers();

services.AddScoped();
}

运行起来多次刷新页面,如下图:

7e6fd035c5fb5926bbf4c8877ce14f20.png

很明显的看到,每次刷 UI 的时候,guid都会变,而在同一个请求 (scope) 中 guid 是一样的。

3. AddTransient

前面大家也看到了,要么作用域是整个进程,要么作用域是一个请求,而这里的 Transient 就没有作用域概念了,注入一次 实例化一次,不信的话上代码给你看呗。


public void ConfigureServices(IServiceCollection services){
services.AddControllers();

services.AddTransient();
}

6d9d96647c64f35330151bab75c6217f.png

从图中可以看到,注入一次就 new 一次,非常简单吧,当然了,各有各的应用场景。

之前不清楚的朋友到现在应该也明白了这三种作用域,接下来继续思考的一个问题就是,这种作用域是如何做到的呢?要想回答这个问题,只能研究源代码了。

三:源码分析

aspnetcore 中的 IOC 容器是 ServiceCollection,你可以向 IOC 中注入不同作用域的类,最后生成 provider,如下代码所示:


var services = new ServiceCollection();

services.AddSingleton();var provider = services.BuildServiceProvider();

1. AddSingleton 的作用域是如何实现的

通常说到单例,大家第一反应就是 static,但是一般 ServiceCollection 中会有成百上千个 AddSingleton 类型,都是静态变量是不可能的,既然不是 static,那就应该有一个缓存字典什么的,其实还真的有这么一个。

1)RealizedServices 字典

每一个 provider 内部都会有一个 叫做 RealizedServices 的字典,这个 字典 将会在后面充当缓存存在, 如下图:

519e0930c59e59bd03d4a4af1513adb4.png

从上图中可以看到,初始化的时候这个字典什么都没有,接下来执行 var orderService = provider.GetService(); 效果如下图:

5892e9596541fdd26d2c37c4ec97bd08.png

可以看到 RealizedServices 中已经有了一个 service 记录了,接着往下执行 var orderService2 = provider.GetService();,最终会进入到 CallSiteRuntimeResolver.VisitCache 方法判断实例是否存在,如下图:

1a88a51d9f9fdf48f339c60dbf9f7675.png

仔细看上面代码的这句话: if (!resolvedServices.TryGetValue(callSite.Cache.Key, out obj)) 一旦字典存在就直接返回,否则就要执行 new 链路,也就是 this.VisitCallSiteMain

综合来看,这就是为什么可以单例的原因,如果不明白可以拿 dnspy 仔细琢磨琢磨。。。

2. AddTransient 源码探究

前面大家也看到了,provider 里面会有一个 DynamicServiceProviderEngine 引擎类,引擎类中用 字典缓存 来解决单例问题,可想而知,AddTransient 内部肯定是没有字典逻辑的,到底是不是呢?调试一下呗。

cef533a4a1be0a7d6a4cc03740ea50eb.png

和单例一样,最终解析都是由 CallSiteRuntimeResolver 负责的,AddTransient 内部会走到 VisitDisposeCache 方法,而这里会一直走 this.VisitCallSiteMain(transientCallSite, context) 来进行 实例的 new 操作,还记得单例是怎么做的吗?它会在这个 VisitCallSiteMain 上包一层 resolvedServices 判断,? 继续追一下 VisitCallSiteMain 方法吧,这个方法最终会走 CallSiteKind.Constructor 分支调用你的构造函数,代码如下:


protected virtual TResult VisitCallSiteMain(ServiceCallSite callSite, TArgument argument){
switch (callSite.Kind)
{
case CallSiteKind.Factory:
return this.VisitFactory((FactoryCallSite)callSite, argument);
case CallSiteKind.Constructor:
return this.VisitConstructor((ConstructorCallSite)callSite, argument);
case CallSiteKind.Constant:
return this.VisitConstant((ConstantCallSite)callSite, argument);
case CallSiteKind.IEnumerable:
return this.VisitIEnumerable((IEnumerableCallSite)callSite, argument);
case CallSiteKind.ServiceProvider:
return this.VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
case CallSiteKind.ServiceScopeFactory:
return this.VisitServiceScopeFactory((ServiceScopeFactoryCallSite)callSite, argument);
}
throw new NotSupportedException(string.Format("Call site type {0} is not supported", callSite.GetType()));
}

最终由 VisitConstructor 对我的实例代码的构造函数进行调用,所以你应该理解了为啥每次注入都会new一次。如下图:

4a2a590a39909d11dcfeddc97471d560.png

3. AddScoped 源码探究

当你明白了 AddSingleton, AddTransient 的原理,我想 Scoped 也是非常容易理解的,肯定是一个 scoped 一个 RealizedServices 对吧,不信的话继续上代码哈。


static void Main(string[] args){
var services = new ServiceCollection();

services.AddScoped();var provider = services.BuildServiceProvider();var scoped1 = provider.CreateScope();var scoped2 = provider.CreateScope();while (true)
{var orderService = scoped1.ServiceProvider.GetService();var orderService2 = scoped2.ServiceProvider.GetService();
Console.WriteLine(orderService);
Thread.Sleep(1000);
}
}

然后看一下 scoped1 和 scoped2 是不是都存在独立的缓存字典。

c6db424c468a8b6b03d12284628aec06.png

从图中可以看到,scoped1 和 scoped2 中的 ResolvedServices 拥有不用的count,也就说明两者是独立存在的,相互不影响。

四:总结

很多时候大家都这么习以为常的用着,突然有一天被问起还是有点懵逼的,所以时常多问自己几个为什么还是很有必要的哈???。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/529250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

权限管理系统_在Gitee狂揽11K Star!这个SpringCloud的权限管理系统你必须知道

SpringCloud 大家都很熟悉了,它作为一套完整的微服务解决方案,广受 Java 开发者们的好评, 今天就为大家介绍一款 Gitee 上的王牌项目,基于 SpringCloud 的权限管理系统——Pig。项目名称:Pig项目作者:pig4c…

导出排除的表_excel拆分实例:如何快速制作考勤统计分析表

编按:面对新的统计需求,很多人会一下变懵,不知如何办。如果涉及的统计有一千多行数据,哭的心思都有了:什么时候才能下班哟!今天老菜鸟通过考勤统计分析表实例分享自己面对新统计需求的解决方法:…

rds 如何学习数据库_如何将本地数据库迁移到云数据库 RDS 上?

使用数据传输服务 ( DTS ) 将本地数据库迁移到 阿里云的云数据库 RDS ,可以实现应用不停服务的情况下,平滑完成数据库的迁移工作。接下来我们将学习下如何使用 DTS 将本地数据库迁移到 RDS 上。背景DTS 支持 SQL Server 数据结构迁移和全量迁移。DTS 支持…

arm ida 伪代码 安卓 符号表_IDA 制作 sig文件 gdb 导入符号表

背景最近比赛遇到了一个题目, 32位静态链接去符号了. 所以用IDA分析的时候很多libc的库函数都无法识别, 就需要在 IDA 中引入 sig 文件. 从而可以识别诸如 read, write, malloc, free 这些库函数. 虽然网上已经有很多制作好的sig文件, 但是还是应该学会自己制作sig文件以备不时…

lua如何打印行号_LUA教程错误信息和回跟踪(Tracebacks)-34

虽然你可以使用任何类型的值作为错误信息,通常情况下,我们使用字符串来描述遇到的错误。如果遇到内部错误(比如对一个非table的值使用索引下标访问)Lua将自己产生错误信息,否则Lua使用传递给error函数的参数作为错误信息。不管在什么情况下&a…

python 套接字 struck_Python socket粘包问题(最终解决办法)

套接字:就是将传输层以下的协议封装成子接口对于应用程序来说只需调用套接字的接口,写出的程序自然是遵循tcp或udp协议的实现第一个功能个:实现:通过客户端向服务端发送命令,调取windows下面的cmd窗口,将服…

python的lib文件夹_python遍历文件夹os.path与pathlib

首先我们来一个需求,这个函数接受文件夹的名称作为输入参数,返回该文件夹中文件的路径,以及其包含文件夹中文件的路径。def print_dir_contents(sPath):import osfor sChild in os.listdir(sPath):sChildPath os.path.join(sPath,sChild)if …

python 数据字典用法_python数据字典的操作

一、什么是字典?字典是Python语言中唯一的映射类型。映射类型对象里哈希值(键,key)和指向的对象(值,value)是一对多的的关系,通常被认为是可变的哈希表。字典对象是可变的,它是一个容器类型,能存储任意个数…

双系统安装deepin20_win10deepin15.10双系统安装教程

第二步:下载深度启动盘制作工具深度启动盘制作工具地址第三步:制作U盘启动盘打开第二部下载的启动盘制作工具,并准备一个u盘插入待装系统的电脑,选择镜像文件后,下一步选择磁盘并勾选格式化磁盘,点下一步开…

ubuntu19 安装git_在Ubuntu 18.04上安装Git

步骤1.首先,通过运行以下命令确保您的系统和apt包列表完全更新:apt-get update -yapt-get upgrade -y第2步。在Ubuntu 18.04上安装Git。现在让我们安装git:apt install git您可以使用以下命令来检查已安装的git版本:$ git --versi…

mysql更新多条数据6_mysql语句:批量更新多条记录的不同值

mysql更新语句很简单,更新一条数据的某个字段,一般这样写:如果更新同一字段为同一个值,mysql也很简单,修改下where即可:这里注意 ‘other_values’ 是一个逗号(,)分隔的字符串,如&am…

php mysql query 行数_如何在PHP中获取MYSQL数据库返回的数据的行数?

展开全部1. mysql_num_rows 可得到e69da5e887aa3231313335323631343130323136353331333337383861查询记录数<?php $con mysql_connect("localhost", "hello", "321");if (!$con){die(Could not connect: . mysql_error());}$db_selected …

mysql数据库开发环境_MacOS下搭载开发环境之数据库篇(Mysql + Navicat)

一、安装Mysql1、官网下载mysql的tar包(提示&#xff1a;建议vpn环境下载)2、解压并安装tar包# 移动解压后的二进制包到安装目录sudo mv mysql-5.7.19-osx10.9-x86_64 /usr/local/mysql# 更改 mysql 安装目录所属用户与用户组cd /usr/localsudo chown -R root:wheel mysql# 初始…

mysql alter 唯一键_MySQL列属性 之 唯一键

MySQL列属性 之 唯一键唯一键唯一键&#xff1a;每张表往往有多个字段需要具有唯一性&#xff0c;数据不能重复&#xff0c;但是在每张表中&#xff0c;只能有一个主键&#xff0c;因此 唯一键就是用来解决表中多个字段需要具有唯一性的问题。例如身份证号码应该每一行的记录不…

如何在mysql中添加复选框_如何使用输入和复选框更新mysql

如果我理解正确,您需要这样做:mysql_query("UPDATE mp3SETaktif 1,baslik " . mysql_escape_string($_POST[baslik]) ."WHERE id $zuha");mysql_query("update mp3 set aktif 1,baslik $_POST[baslik]where id $_POST[id]")现在唯一的区别…

mysql 检查列是否存在,如何检查mysql表列是否存在?

How can I check if mysql table field even exists ?The column name is price and I need to see if it exists.Havent understood really how the EXISTS works...Any examples or ideas ?Thanks解决方案In PHP:$fields mysql_list_fields(database_name, table_name);$c…

mysql proxy yum_mysql 高可用架构 proxysql 之一 yum安装

os:centos 7.4mysql: 5.7proxysql: 1.4.10ip 规划如下&#xff1a;192.168.56.101 node1 (proxysql)192.168.56.102 node2 (mysql master)192.168.56.103 node3 (mysql slave)192.168.56.104 node4 (mysql slave)安装mysql 5.7node2、node3、node4 安装 mysql 5.7 software详细…

wpf 使用位图画图为什么断断续续_WPF的未来是微软WinUi!

WPF(Windows Presentation Foundation)是微软推出的基于Windows 的用户界面框架&#xff0c;属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架&#xff0c;真正做到了分离界面设计人员与开发人员的工作&#xff1b;同时它提供了全新的多媒体交互用户图形界…

antd新增一行页码不正确_antd-Table@4.x对rowKey属性的重构

时间&#xff1a;2020/04/26 &#xff0c;转载请注明出处。写在前面antd团队于2020年2月发布了酝酿已久的antd4.0版本&#xff0c;对样式的调整、部分组件逻辑的重构都进行了较大改动&#xff0c;本文针对Table的rowKey属性重构作分析。由一个mistake带来的思考在数据治理模块的…

qt调用mysql调用了存储过_Qt调用Server SQL中的存储过程

Server SQL中的存储过程如下&#xff1a;CREATE procedure PINSERTPCpcnum int,pcname varchar(50),pctype int,ipaddress varchar(50),port int,pcid int outputas--declare pcid intif exists (select * from COMPUTERTABLE where PcNum pcnum)set pcid -1elsebegininser…