一:背景

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

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

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

2. AddScoped

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

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

            services.AddScoped();        }

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

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

3. AddTransient

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

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

            services.AddTransient();        }

从图中可以看到,注入一次就 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 的字典,这个 字典 将会在后面充当缓存存在, 如下图:

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

可以看到 RealizedServices 中已经有了一个 service 记录了,接着往下执行 var orderService2 = provider.GetService();,最终会进入到 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 判断,? 继续追一下 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一次。如下图:

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 是不是都存在独立的缓存字典。

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

四:总结

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

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

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

    一:背景 1. 讲故事 前几天有位朋友让我有时间分析一下 aspnetcore 中为什么向 ServiceCollection 中注入的 Class 可以做到 Singleton,Transient, ...

  2. jsp 使用base标签 没有作用_终于弄明白衣服上,使用前请移除的标签到底是什么,起什么作用...

    点击上方"机械设计一点通"关注我们,每天学习一个机械设计相关知识点 发现买的T恤上,连在衣服上有个标签,上面写着使用前请移除.里面有个一硬条状物体,不知道是什么,很好奇,便把它拆开 ...

  3. ThreadLocal原理详解--终于弄明白了ThreadLocal

    ThreadLocal原理详解 在我看到ThreadLocal这个关键字的时候我是懵逼的,我觉得我需要弄明白,于是,我就利用搜索引擎疯狂查找,试图找到相关的解答,但是结果不尽人意. 首先说一下我的理解 ...

  4. java的向下转型_终于搞明白向下转型的作用了,还不懂的进来看下.

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 之前一直搞不明白,向下转型的实际意义,虽然知道向下转型怎么写, 现在我来讲解下 向下转型 的实际作用,如果有错的话,大家可以指出, 如果认为我说的对的话可 ...

  5. codesys 串口通讯实例_串口通信RS232的基本接法,原来这么简单,今天终于弄明白了...

    目前较为常用的串口有9针串口(DB9)和25针串口(DB25),通信距离较近时(<12m),可以用电缆线直接连接标准RS232端口(RS422,RS485较远),若距离较远,需附加调制解调器(M ...

  6. 终于弄明白了二极管在BUCK与BOOST电路中的作用!

    今天在和同事讨论BOOST电路时,被问到二极管在电路中的作用.这个应该很熟悉才对,但是当时却无法立刻给出回答,所以下班回来翻了翻笔记,整理了二极管在BUCK电路和BOOST电路中的作用,不敢独享,所以 ...

  7. java web servlet、servlet容器 HTTP服务器和mvc三层架构或者说servlet属于哪一层的,给我搞的晕晕的,今天终于弄明白了

    0 我们先看Web容器是什么? 首先,让我们简单回顾一下web技术的发展历史,可以帮助你理解web容器的由来. 早期的web应用主要用于浏览新闻等静态页面,HTTP服务器(比如Apache,Nginx ...

  8. 各层作用_土工布有什么作用呢?

    土工布是由高强纤维丝束与无纺布复合编织而成,其工艺是纤维束平直排列,充分发挥纱线的受力作用.无纺布垫在其下,经编技术将其缠绕捆扎,使纤维丝束无纺布固结在一起,既保持无纺布的反滤,又具有机织布的强度. ...

  9. 车速表 html 效果,车速表速度显示的问题,终于弄明白了!

    一直以来对车速表速度显示总是心存疑惑,因为车上显示的速度GPS显示的总有一定的误差,看了一些帖子总结,终于明白了. 首先(别人的帖子): 测速仪 根据中华人民共国国家标准(GB/T21255- ...

最新文章

  1. Linux下SVN服务器支持Apache的http和svnserve独立服务器
  2. sql floor 取整函数
  3. Android可输入的下拉框,android 可编辑的下拉框 Demo
  4. DL之SqueezeNet:SqueezeNet算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
  5. 字节跳动2018校招算法方向(第一批) —— 1-最外层点
  6. javascript学习系列(2):数组中的filter方法
  7. ie678,FF,chrome的css兼容性
  8. 【Java并发编程】之二:线程中断
  9. 八、开发者工具和指南(三) Source code organization
  10. 大规模中文自然语言处理语料(百科,问答、新闻,翻译)
  11. Windows Server 2008 R2 远程桌面服务RDS和VDI介绍
  12. 校园卡管理系统实验报告c语言,校园卡管理系统-C语言.doc
  13. 基于Springboot的个人健康监控管理系统 毕业论文+项目源码、
  14. 一元三次方程求解matlab_为什么一元n次代数方程必有n个根?
  15. 《正念领导力》承诺14:创造共赢
  16. 沃邮箱的服务器设置,沃邮箱Outlook和Foxmail设置收发邮件
  17. avr单片机流水灯程序c语言,AVR单片机综合流水灯C程序
  18. 不同速度流体的剪切形成不同尺度的漩涡,看起来很像分形。
  19. word在线编辑 linux,Office Online Server 在线编辑Office文档,安装部署
  20. Janus之自问自答

热门文章

  1. 移动云2020 H1营收44.57亿元,同比增长556.4%
  2. 图解分布式架构的发展和演进 | 技术干货
  3. 开源不止,前进不息:2018 OpenInfra Days China来了!
  4. c语言spi测试代码,spi_test.c的spi跟踪(spi 数据传送流程)
  5. oracle redo 200mb,Oracle的redo log在各场景下的恢复
  6. 12123两小时没付款怎么办_机械厂上班的男朋友,一天十小时,周末不休,没时间陪我怎么办?...
  7. RuoYi-Cloud 登陆 /code 获取验证码出错
  8. Redis Client On Error: Error: connect ECONNREFUSED 192.168.xxx.105:6379 Config right?
  9. springboot 通过url访问本地文件
  10. Java并行流 No thread-bound request found