Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

今天就跟大家聊聊有關(guān)Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

創(chuàng)新互聯(lián)建站服務(wù)項(xiàng)目包括銅川網(wǎng)站建設(shè)、銅川網(wǎng)站制作、銅川網(wǎng)頁制作以及銅川網(wǎng)絡(luò)營(yíng)銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,銅川網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到銅川省份的部分城市,未來相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!

一:背景

1. 講故事

前幾天有位朋友讓我有時(shí)間分析一下 aspnetcore 中為什么向 ServiceCollection 中注入的 Class 可以做到 Singleton,Transient,Scoped,挺有意思,這篇就來聊一聊這一話題,自從 core 中有了 ServiceCollection, 再加上流行的 DDD 模式,相信很多朋友的項(xiàng)目中很少能看到 new 了,好歹 spring 十幾年前就是這么干的。

二:Singleton,Transient,Scoped 基本用法

分析源碼之前,我覺得有必要先介紹一下它們的玩法,為方便演示,我這里就新建一個(gè) webapi 項(xiàng)目,定義一個(gè) interface 和 concrete ,代碼如下:

    public class OrderService : IOrderService
    {
        private string guid;

        public OrderService()
        {
            guid = $"時(shí)間:{DateTime.Now}, guid={ Guid.NewGuid()}";
        }

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

    public interface IOrderService
    {
    }

1. AddSingleton

正如名字所示它可以在你的進(jìn)程中保持著一個(gè)實(shí)例,也就是說僅有一次實(shí)例化,不信的話代碼演示一下哈。

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

            services.AddSingleton<IOrderService, OrderService>();
        }
    }

    [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";
        }
    }

接著運(yùn)行起來多次刷新頁面,如下圖:

Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

可以看到,不管你怎么刷新頁面,guid都是一樣,說明確實(shí)是單例的。

2. AddScoped

正從名字所述:Scope 就是一個(gè)作用域,那在 webapi 或者 mvc 中作用域是多大呢? 對(duì)的,就是一個(gè)請(qǐng)求,當(dāng)然請(qǐng)求會(huì)穿透 Presentation, Application, Repository 等等各層,在穿層的過程中肯定會(huì)有同一個(gè)類的多次注入,那這些多次注入在這個(gè)作用域下維持的就是單例,如下代碼所示:

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

            services.AddScoped<IOrderService, OrderService>();
        }

運(yùn)行起來多次刷新頁面,如下圖:

Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

很明顯的看到,每次刷 UI 的時(shí)候,guid都會(huì)變,而在同一個(gè)請(qǐng)求 (scope) 中 guid 是一樣的。

3. AddTransient

前面大家也看到了,要么作用域是整個(gè)進(jìn)程,要么作用域是一個(gè)請(qǐng)求,而這里的 Transient 就沒有作用域概念了,注入一次 實(shí)例化一次,不信的話上代碼給你看唄。

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

            services.AddTransient<IOrderService, OrderService>();
        }

Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

從圖中可以看到,注入一次就 new 一次,非常簡(jiǎn)單吧,當(dāng)然了,各有各的應(yīng)用場(chǎng)景。

之前不清楚的朋友到現(xiàn)在應(yīng)該也明白了這三種作用域,接下來繼續(xù)思考的一個(gè)問題就是,這種作用域是如何做到的呢? 要想回答這個(gè)問題,只能研究源代碼了。

三:源碼分析

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

var services = new ServiceCollection();

            services.AddSingleton<IOrderService, OrderService>();

            var provider = services.BuildServiceProvider();

1. AddSingleton 的作用域是如何實(shí)現(xiàn)的

通常說到單例,大家第一反應(yīng)就是 static,但是一般 ServiceCollection 中會(huì)有成百上千個(gè) AddSingleton 類型,都是靜態(tài)變量是不可能的,既然不是 static,那就應(yīng)該有一個(gè)緩存字典什么的,其實(shí)還真的有這么一個(gè)。

1)RealizedServices 字典

每一個(gè) provider 內(nèi)部都會(huì)有一個(gè) 叫做 RealizedServices 的字典,這個(gè) 字典 將會(huì)在后面充當(dāng)緩存存在, 如下圖:

Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

從上圖中可以看到,初始化的時(shí)候這個(gè)字典什么都沒有,接下來執(zhí)行 var orderService = provider.GetService<IOrderService>(); 效果如下圖:

Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

可以看到 RealizedServices 中已經(jīng)有了一個(gè) service 記錄了,接著往下執(zhí)行 var orderService2 = provider.GetService<IOrderService>();,最終會(huì)進(jìn)入到 CallSiteRuntimeResolver.VisitCache 方法判斷實(shí)例是否存在,如下圖:

Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

仔細(xì)看上面代碼的這句話: if (!resolvedServices.TryGetValue(callSite.Cache.Key, out obj)) 一旦字典存在就直接返回,否則就要執(zhí)行 new 鏈路,也就是 this.VisitCallSiteMain。

綜合來看,這就是為什么可以單例的原因,如果不明白可以拿 DNSpy 仔細(xì)琢磨琢磨。。。

2. AddTransient 源碼探究

前面大家也看到了,provider 里面會(huì)有一個(gè) DynamicServiceProviderEngine 引擎類,引擎類中用 字典緩存 來解決單例問題,可想而知,AddTransient 內(nèi)部肯定是沒有字典邏輯的,到底是不是呢? 調(diào)試一下唄。

Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

和單例一樣,最終解析都是由 CallSiteRuntimeResolver 負(fù)責(zé)的,AddTransient 內(nèi)部會(huì)走到 VisitDisposeCache 方法,而這里會(huì)一直走 this.VisitCallSiteMain(transientCallSite, context) 來進(jìn)行 實(shí)例的 new 操作,還記得單例是怎么做的嗎? 它會(huì)在這個(gè) VisitCallSiteMain 上包一層 resolvedServices 判斷,???? 繼續(xù)追一下 VisitCallSiteMain 方法吧,這個(gè)方法最終會(huì)走 CallSiteKind.Constructor 分支調(diào)用你的構(gòu)造函數(shù),代碼如下:

		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 對(duì)我的實(shí)例代碼的構(gòu)造函數(shù)進(jìn)行調(diào)用,所以你應(yīng)該理解了為啥每次注入都會(huì)new一次。如下圖:

Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

3. AddScoped 源碼探究

當(dāng)你明白了 AddSingleton, AddTransient 的原理,我想 Scoped 也是非常容易理解的,肯定是一個(gè) scoped 一個(gè) RealizedServices 對(duì)吧,不信的話繼續(xù)上代碼哈。

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

            services.AddScoped<IOrderService, OrderService>();

            var provider = services.BuildServiceProvider();

            var scoped1 = provider.CreateScope();
            
            var scoped2 = provider.CreateScope();

            while (true)
            {
                var orderService = scoped1.ServiceProvider.GetService<IOrderService>();

                var orderService2 = scoped2.ServiceProvider.GetService<IOrderService>();

                Console.WriteLine(orderService);

                Thread.Sleep(1000);
            }
        }

然后看一下 scoped1 和 scoped2 是不是都存在獨(dú)立的緩存字典。

Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的

從圖中可以看到,scoped1 和 scoped2 中的 ResolvedServices 擁有不用的count,也就說明兩者是獨(dú)立存在的,相互不影響。

看完上述內(nèi)容,你們對(duì)Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。

網(wǎng)頁標(biāo)題:Singleton,Transient,Scoped的作用域是如何實(shí)現(xiàn)的
本文地址:http://muchs.cn/article24/gppsce.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號(hào)、品牌網(wǎng)站建設(shè)、品牌網(wǎng)站設(shè)計(jì)服務(wù)器托管、面包屑導(dǎo)航動(dòng)態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化