各層作用_終于弄明白了 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.pswp.cn/news/529250.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/529250.shtml
英文地址,請注明出處:http://en.pswp.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…