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

终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的,第1张

概述一:背景 1. 讲故事 前几天有位朋友让我有时间分析一下 aspnetcore 中为什么向 ServiceCollection 中注入的 Class 可以做到 Singleton,Transient, 一:背景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<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";        }    }

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

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

2. AddScoped

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

        public voID ConfigureServices(IServiceCollection services)        {            services.AddControllers();            services.AddScoped<IOrderService,OrderService>();        }

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

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

3. AddTransIEnt

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

        public voID ConfigureServices(IServiceCollection services)        {            services.AddControllers();            services.AddTransIEnt<IOrderService,OrderService>();        }

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

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

三:源码分析

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

            var services = new ServiceCollection();            services.AddSingleton<IOrderService,OrderService>();            var provIDer = services.BuildServiceProvIDer();
1. AddSingleton 的作用域是如何实现的

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

1)RealizedServices 字典

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

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

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

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

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

2. AddTransIEnt 源码探究

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

和单例一样,最终解析都是由 CallSiteRuntimeResolver 负责的,AddTransIEnt 内部会走到 VisitdisposeCache 方法,而这里会一直走 this.VisitCallSiteMain(transIEntCallSite,context) 来进行 实例的 new *** 作,还记得单例是怎么做的吗? 它会在这个 VisitCallSiteMain 上包一层 resolvedServices 判断, 总结

以上是内存溢出为你收集整理的终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的全部内容,希望文章能够帮你解决终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1213788.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-04
下一篇 2022-06-04

发表评论

登录后才能评论

评论列表(0条)

保存