终于弄明白了 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<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 的作用域是如何实现的的更多相关文章
- 关于java中是引用传递还是值传递的问题!!!经常在笔试中遇到,今天终于弄明白了!
关于JAVA中参数传递问题有两种,一种是按值传递(如果是基本类型),另一种是按引用传递(如果是對象).首先以两个例子开始:1)public class Test2 { public static vo ...
- PID算法终于弄明白原理了,原来就这么简单
看起来PID高大尚,实则我们都是被他的外表所震撼住了.先被别人唬住,后被公式唬住,由于大多数人高数一点都不会或者遗忘,所以再一看公式,简直吓死.了解了很浅的原理后,结果公式看不懂,不懂含义,所以最终没 ...
- 深入理解net core中的依赖注入、Singleton、Scoped、Transient(一)
相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transient(一) 深入理解net core中的依赖注入.Singleton.Scoped.Transient ...
- 深入理解net core中的依赖注入、Singleton、Scoped、Transient(三)
相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transient(一) 深入理解net core中的依赖注入.Singleton.Scoped.Transient ...
- .net core 注入中的三种模式:Singleton、Scoped 和 Transient
从上篇内容不如题的文章<.net core 并发下的线程安全问题>扩展认识.net core注入中的三种模式:Singleton.Scoped 和 Transient 我们都知道在 Sta ...
- 深入理解net core中的依赖注入、Singleton、Scoped、Transient(四)
相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transient(一) 深入理解net core中的依赖注入.Singleton.Scoped.Transient ...
- 深入理解net core中的依赖注入、Singleton、Scoped、Transient(二)
相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transient(一) 深入理解net core中的依赖注入.Singleton.Scoped.Transient ...
- 深入理解net core中的依赖注入、Singleton、Scoped、Transient(四)【转】
原文链接:https://www.cnblogs.com/gdsblog/p/8465401.html 相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transi ...
- 几张图弄明白ios布局中的尺寸问题
背景 先说说逆向那事.各种曲折..各种技术过时,老老实实在啃看雪的帖子..更新会有的. 回正题,这里讨论的是在Masnory框架下的布局问题.像我这种游击队没师傅带,什么都得自己琢磨,一直没闹明白下面 ...
随机推荐
- Hadoop学习问题记录之基础篇
目的 记录学习hadoop过程中遇到的基础问题,无关大小.无关困扰时间长短. 问题一 全分布式环境中运行mapred程序,报异常:java.net.NoRouteToHostException: 没有 ...
- 数据量大了一定要分表,分库分表组件Sharding-JDBC入门与项目实战
最近项目中不少表的数据量越来越大,并且导致了一些数据库的性能问题.因此想借助一些分库分表的中间件,实现自动化分库分表实现.调研下来,发现Sharding-JDBC目前成熟度最高并且应用最广的Java分 ...
- JavaScript动画实例:圆点的衍生
考虑如下的曲线方程: R=S*sqrt(n) α=n*θ X=R*SIN(α) Y=R*COS(α) 其中,S和θ可指定某一个定值.对n循环取0~999共1000个值,对于每个n,按照给定的坐标方程, ...
- AutoMapper 9.0的改造(续)
上一篇有一个读者,有疑问,如何自动化注册Dto 我开篇,做了一个自动化注册的 public sealed class AutoInjectAttribute : Attribute { public ...
- 双下划线开头的attr方法
# class Foo: # x=1 # def __init__(self,y): # self.y=y # # def __getattr__(self, item): # print('执行__ ...
- 网络安全传输系统-sprint2线程池技术优化
part1:线程池工作原理 为满足多客户端可同时登陆的要求,服务器端必须实现并发工作方式.当服务器主进程持续等待客户端连接时,每连接上一个客户端都需一个单独的进程或线程处理客户端的任务.但考虑到多进程 ...
- Jmeter(二十) - 从入门到精通 - JMeter监听器 -下篇(详解教程)
1.简介 监听器用来监听及显示JMeter取样器测试结果,能够以树.表及图形形式显示测试结果,也可以以文件方式保存测试结果,JMeter测试结果文件格式多样,比如XML格式.CSV格式.默认情况下,测 ...
- 10、Java 数组的定义和使用
1.数组的定义 首先举一个小例自:如果你现在需要定义100个int类型的变量,那么按照前俩节的做法为: int a = 1, b=2 , c=3.......; 可以发现我们的代码特别的冗余,而且不美 ...
- Go语言入门系列(五)之指针和结构体的使用
Go语言入门系列前面的文章: Go语言入门系列(二)之基础语法总结 Go语言入门系列(三)之数组和切片 Go语言入门系列(四)之map的使用 1. 指针 如果你使用过C或C++,那你肯定对指针这个概念 ...
- C#LeetCode刷题之#532-数组中的K-diff数对(K-diff Pairs in an Array)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3716 访问. 给定一个整数数组和一个整数 k, 你需要在数组里找 ...