ASP.NET Core 沉思录 - ServiceProvider 的二度出生

ASP.NET Core 终于将几乎所有的对象创建工作都和依赖注入框架集成了起来。并对大部分的日常工作进行了抽象。使得整个框架扩展更加方便。各个部分的集成也更加容易。今天我们要思考的部分仍然是从一段每一个工程中都大同小异的代码开始的。

IWebHostBuilder CreateWebHostBuilder(string[] args)
{
    return new WebHostBuilder()
        .UseKestrel(ko => ko.AddServerHeader = false)
        .ConfigureAppConfiguration(cb => cb.AddCommandLine(args))
        .ConfigureLogging(lb => {...})
        .UseStartup<Startup>();
}

0 太长不读

640?wx_fmt=jpeg

  • ASP.NET Core 的初始化包含了两个步骤:第一个步骤是 Hosting 相关服务的初始化过程,初始化完毕之后创建了第一个 IServiceProvider 对象;第二步是 Application 相关服务的初始化过程。而 Application 的初始化过程可以注入 Hosting 相关的服务。之后,通过 IStartup.ConfigureServices 方法创建了第二个 IServiceProvider 对象。

  • 初始化过程中创建的两个 IServiceProvider 均会跟随 WebHost 的销毁而销毁。

  • 通过 Startup 类型的构造函数注入的实例是由 Hosting 初始化阶段创建的 IServiceProvider 创建的。只能注入 Hosting 初始化阶段添加的类型。且最好不要使用大量消耗资源的类型。

  • 可以在 Startup.Configure 方法中添加其他参数,这样会使用 Application 的一个 Scope 下的 IServiceProvider 进行注入,且在方法调用完毕之后该 Scope 即被销毁。因此该方法内可以创建资源占用量较高的需要 Dispose 的类型实例而不造成泄露。

1 WebHost 的构建主要就是向 `IServiceCollection` 中添加服务

之前提到过,任何 Framework 只有两件事情,第一件事情就是对象怎么创建,第二件事情就是如何将这些创建出来的对象塞到 Framework 处理流水线中。因此 ASP.NET Core 也是这样。在应用程序启动的时候,我们会在 WebHostBuilder.Build 方法调用之前进行各种各样的操作,虽然我们调用的大部分操作都是扩展方法(例如上述代码中的 UseXxx,和 ConfigureLogging),但是归根结底会调用 IWebHostBuilder 的以下方法:

IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate);
IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices);
IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices);

不论调哪一个方法,它们做的事情其实都是一件。就是告诉应用程序,我到底有哪些对象需要创建,如何创建这些对象,以及其生存期如何管理。从技术角度上来说,就是将需要创建的对象类型添加到 IServiceCollection 中。如果感兴趣的同学可以看看 WebHostBuilder 的实现代码(https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilder.cs),就更加清晰了。

例如,以 ConfigureLogging 为例,代码请参见这里(https://github.com/aspnet/Extensions/blob/master/src/Logging/Logging/src/LoggingServiceCollectionExtensions.cs):

public static IWebHostBuilder ConfigureLogging(
    this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, 
    ILoggingBuilder> configureLogging
)
{
    return hostBuilder.ConfigureServices((context, collection) => 
        collection.AddLogging(builder => configureLogging(context, builder)));
}

public static IServiceCollection AddLogging(
    this IServiceCollection services, 
    Action<ILoggingBuilder> configure
)
{
    if (services == null) { throw new ArgumentNullException(nameof(services)); }

    services.AddOptions();
    services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
    services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
    services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>(
        new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));
    configure(new LoggingBuilder(services));
    return services;
}

可以看到实际上就是将 IOptions<>IOptionsSnapshot<>IOptionsMonitor<>IOptionsFactory<>IOptionsMonitorCache<> 以及 ILoggerFactoryILogger<>IConfigureOptions<LoggerFilterOptions> 添加到 IServiceCollection 中的过程。有关日志的内容我们会在另一篇文章中介绍。

2 Startup 初始化时为什么又能注入又有 `IServiceCollection` 呢

WebHost 的构建过程中,十有八九会出现 UseStartup 这句话(如果不出现这句话,那么很大程度上使用了 Configure 扩展方法)。Startup 是整个 Web 应用程序的起点。应用程序(Web App)托管在宿主(Hosting Environment)中。那么它应当是在初始化的最终阶段执行的。我们来观察一下它的典型结构:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    
{
        // Add application related services to service collection.
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    
{
        // Create application pipeline. We will not focus on this method.
    }
}

如果单纯观察上述代码那么并没有任何的稀奇之处。ConfigureServices 方法将应用需要的类型全部添加到 IServiceCollection 实例中,而 Configure 来构建 Pipeline(我们此次不讨论该方法)。但是如果我们需要记录日志,读取配置文件,在应用程序生命周期事件中注册新的处理方法时,我们可以将其直接注入 Startup 中。例如:

public class Startup
{
    readonly IConfiguration configuration;
    readonly IApplicationLifetime lifetime;
    readonly ILogger<Startup> logger;

    public Startup(
        IConfiguration configuration, IApplicationLifetime lifetime, ILogger<Startup> logger
)
    
{
        this.configuration = configuration;
        this.lifetime = lifetime;
        this.logger = logger;
    }

    public void ConfigureServices(IServiceCollection services)
    
{
        // Add application related services to service collection.
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    
{
        // Create application pipeline.
    }
}

那么问题就来了。

  • Startup 中注入的 configurationlifetimelogger 这些服务是由哪一个 IServiceProvider 创建出来的呢?

  • 如果在 Startup 创建时 IServiceProvider 已然创建,那么 Startup.ConfigureServices 在向哪个 IServiceCollection 实例添加类型呢?

  • 应用程序运行期间的 IServiceProvider 是在 Startup 创建之前就创建好的那个呢、还是由 Startup 配置的 IServiceCollection 实例创建的那个呢?

3 两阶段 ServiceProvider 创建

既然 Startup 中已经有一个 IServiceProvider 来给相应的类型进行依赖注入,而平时的应用程序中的依赖注入又能够包含 Startup.ConfigureServices 中的类型定义,那么说明在整个初始化过程中先后创建了两个 IServiceProvider 对象。

即 ASP.NET Core 的初始化包含了两个步骤:

  • 第一个步骤是 Hosting 相关服务的初始化过程,初始化完毕之后创建了第一个 IServiceProvider 对象;

  • 第二步是 Application 相关服务的初始化过程。而 Application 的初始化过程可以注入 Hosting 相关的服务。之后,通过 IStartup.ConfigureServices 方法创建了第二个 IServiceProvider 对象。

如果你对源代码感兴趣

请参考 WebHostBuilder 类的 Build 方法(源代码在这里:https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/WebHostBuilder.cs)。大致的过程如下:

  • BuildCommonServices 方法将所有 Hosting 所需的服务(WebHost 相关类型以及所有 IWebHostBuilder 调用中添加的服务类型)添加到 IServiceCollection 对象中。

  • 使用该 IServiceCollection 创建 Hosting 相关的 IServiceProvider,不妨称之为 hostingServiceProvider

  • 使用该 hostingServiceProvider 创建 IStartup 对象(这里有和环境相关的 Convension,详情请参见上一篇)。

  • 使用一个复制的 IServiceCollection 对象调用 IStartup.ConfigureServices 方法创建另外一个 IServiceProvider 不妨称之为 applicationServiceProvider

在了解了上述过程之后,那么我们需要注意些什么呢?

首先我们已经了解,Startup 可以使用 Hosting 的 IServiceProvider 进行注入。但是 IServiceProvider 是一个顶级的 Provider,如果我们在 Startup 中创建了一个非常消耗资源的对象(实现了 IDisposable),则在默认情况下该对象只有在应用程序彻底退出的时候才会销毁。若显式 Dispose 该对象的话且该对象不是 Transient Scope。则有可能导致 Defect。

4 规避初始化过程中的资源泄露

但是如果我真的需要在初始化的时候注入非常消耗资源的对象,而我又希望规避资源的泄露,我该怎么办呢?其实还是有办法的。那就是不使用 Startup 的构造函数进行注入而是直接在 Configure 方法中通过参数进行注入。

为什么这种方式可以规避资源泄露呢?因为这种注入机智并非典型的依赖注入机制,而是 ASP.NET Core 特意实现的。如果应用程序在初始化时使用的 UseStartup<TStartup>() 中的 TStartup 并没有实现 IStartup 的话,ASP.NET Core 就会使用基于约定的 IStartup 实现对 TStartup 进行包装。在包装过程中,它会尝试找到 TStartup 类型中的 Configure 方法,检查参数表中的参数,并使用 IStartup.ConfigureServices 创建的 IServiceProvider 进行注入。但是这里的 IServiceProvider 却并不初始化过程中的顶级 Provider。而是在将整个方法调用包裹在了 Scope 里。因此即使在初始化过程中创建非常消耗资源的实例也会随着方法调用结束后 ScopeDispose 而销毁。具体代码请参见:ConfigureBuilder 源代码 (https://github.com/aspnet/AspNetCore/blob/master/src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs)

5 总结

请飞到文章开头的第 0 节 :-D。

如果您觉得本文对您有帮助,也欢迎分享给其他的人。我们一起进步。欢迎关注我的博客(https://clrdaily.com)和微信公众号:

640?wx_fmt=jpeg


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316752.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[SPOJ - FTOUR2] Free tour II(点分治 + 背包dp + 启发式合并)

SPOJ - FTOUR2 Free tour II problem 给定一棵树&#xff0c;以及 mmm 个拥挤城市编号&#xff0c;选择一条最多包含 kkk 个拥挤城市的简单路径。 每条边有一个有趣度 www&#xff0c;可正可负。简单路径的价值定义为包含边的有趣度之和。 求最大价值。n≤2e5,∣w∣≤1e4n\…

PKUSC2022 游记

前言 1001822698014095235。 题的难度感觉比去年难不少&#xff0c;主要的体现在于两天的T1都没有之前那么可做了&#xff0c;在信息差的影响下几乎成了两场崩盘场。 由于比赛时间变短的原因&#xff0c;模拟反而比历年简单不少。 遗憾&#xff1a; d1t3的网络流莫名其妙无法…

Acwing 216. Rainbow的信号

Acwing 216. Rainbow的信号 题意&#xff1a; 给你n个数&#xff0c;在这n个数中&#xff0c;等概率地选取两个数l&#xff0c;r&#xff0c;如果l>r,则交换l,r 把信号中的第 l 个数到第 r 个数取出来&#xff0c;构成一个数列 P。 A 部分对话的密码是数列 P 的 xor 和的…

微软开源故事 | 开启 .NET 开源革命

如今&#xff0c;在微软构建开源软件是很正常的一件事——但早在2007年&#xff0c;我开始在微软工作时&#xff0c;情况并非如此。我们花了几年的时间才找到正确的方法&#xff0c;顺利开启了微软的开源之路。但是&#xff0c;如今我们已取得胜利&#xff0c;可以面带微笑地回…

【无码专区9】序列统计(带权并查集 + 前缀和建边 + dp)

因为只有std&#xff0c;没有自我实现&#xff0c;所以是无码专区 主要是为了训练思维能力 solution才是dls正解&#xff0c;但是因为只有潦草几句&#xff0c;所以大部分会有我自己基于正解上面的算法实现过程&#xff0c;可能选择的算法跟std中dls的实现不太一样。 std可能…

Acwing 217. 绿豆蛙的归宿

Acwing 217. 绿豆蛙的归宿 题意&#xff1a; 给出一个有向无环的连通图&#xff0c;起点为 1&#xff0c;终点为 N&#xff0c;每条边都有一个长度。 数据保证从起点出发能够到达图中所有的点&#xff0c;图中所有的点也都能够到达终点。 绿豆蛙从起点出发&#xff0c;走向…

LNOI2022:游记

前言 The world is cruel. 真的为身边的一些人感到可惜… Day -1 PKUSC刚刚考完&#xff0c;然后就要省选了&#xff1f; 板子实在是看的够够的了。 然而还是不想深度做题&#xff0c;看了看APIO的practise&#xff0c;T1写完发现偶数还得特别做&#xff0c;就感觉很麻烦&…

合肥.NET技术社区首次线下聚会全程回顾【多图】

2019年3月16日对于合肥.NET来说是一个特别的日子&#xff0c;因为这是合肥.NET技术社区首次非正式线下聚会&#xff01;这次聚会受场地限制&#xff08;毕竟是聚餐的形式&#xff09;&#xff0c;即使换成了小椅子后&#xff0c;最多也只能容纳24个人&#xff0c;所以还有一些小…

【无码专区10】第K大查询(双向链表 /主席树+st表)

已自我实现&#xff0c;但还是归入无码专区序列。哈哈哈哈哈 对于my idea部分&#xff0c;我的每一个想法都实现了&#xff0c;可供参考。 problem 给定一个 1∼n1\sim n1∼n 的排列和 kkk&#xff0c;求所有 r−l1≥kr-l1\ge kr−l1≥k 的区间 [l,r][l,r][l,r] 中的第 kkk 大…

SignalR第一节-在5分钟内完成通信连接和消息发送

前言首先声明&#xff0c;这又是一个小白从入门到进阶系列。 SignalR 这个项目我关注了很长时间&#xff0c;中间好像还看到过微软即将放弃该项目的消息&#xff0c;然后我也就没有持续关注了&#xff0c;目前的我项目中使用的是自己搭建的 WebSocket &#xff0c;连接管理和消…

P5327 [ZJOI2019]语言(线段树合并、生成树)

解析 只会扫描线树剖的三只log&#xff08;悲 考虑对每个 uuu 考虑合法的 vvv 的集合&#xff0c;必然是一个联通块。 进一步的&#xff0c;观察到这个联通块就是由所有经过 uuu 的路径的端点形成的最小生成树。 我们有一个最小生成树的经典结论&#xff1a;最小生成树边权和…

【学习笔记】信息学竞赛中的概率与期望小结

信息竞赛——概率与期望事件事件的蕴含、包含事件的互斥事件的对立事件的和&#xff08;并&#xff09;事件的积&#xff08;交&#xff09;事件的差概率事件的独立性全概率公式贝叶斯公式概率DP&#xff08;竞赛中的考察&#xff09;期望&#xff08;竞赛中的考察&#xff09;…

Acwing 218. 扑克牌

Acwing 218. 扑克牌 题意&#xff1a; 一副扑克牌(54张)&#xff0c;问得到A 张黑桃、B 张红桃、C 张梅花、D 张方块需要翻开的牌的张数的期望值 E 是多少&#xff1f; 如果翻开的牌是大王或者小王&#xff0c;Admin 将会把它作为某种花色的牌放入对应堆中&#xff0c;使得放…

尝试:Script Lab,快速 O365 开发工具//SL01)

《前言》Script Lab 我希望有一个系列&#xff08;连载&#xff09;&#xff0c;可是我挺担心没偿没有能力去驾驭它。虽然早年前己经接触过&#xff0c;但一直未有下决心开始 Office 365 的开发之旅&#xff0c;虽然一直被光标老师所鼓舞&#xff0c;但是我心有旁骛还没有真正做…

P3710 方方方的数据结构(kd-tree)

解析 写吐了… 一开始觉得线段树分治直接做就行简直是个伞兵题&#xff0c;写完挂掉才想起来线段树分治会打乱操作顺序导致全假… 重构吧&#xff01; 炸裂之下去贺题解&#xff0c;std做法 O(mmlog⁡m)O(m\sqrt m\log m)O(mm​logm) 令人谔谔&#xff0c;但kd-tree做法确实挺…

[POJ 3709] K-Anonymous Sequence(斜率优化dp / 动态维护凸包)

K-Anonymous Sequence看了两年前自己的博客&#xff0c;真的好青涩&#xff0c;连 markdown 都不太会用。 确实观赏感不是很好。 学习真的是慢慢积累的过程&#xff0c;以前感觉理解很困难的事&#xff0c;随着知识的增长&#xff0c;现在都基本悟了。 problem POJ3709 so…

Keiichi Tsuchiya the Drift King

Keiichi Tsuchiya the Drift King 题意&#xff1a; 给定一辆小车长宽分别为 b&#xff0c;a&#xff0c;轨道的圆弧部分半径为 r&#xff0c;圆弧对应的角度为 d&#xff0c;求出小车能通过轨道的最小轨道宽度 w。 题解&#xff1a; 我们考虑小车处于什么状态会使弯道最宽…

AspNet Core 下利用普罗米修斯+Grafana构建Metrics和服务器性能的监控

概述Prometheus是一套开源的监控&报警&时间序列数据库的组合,起始是由SoundCloud公司开发的。该项目有非常活跃的社区和开发人员&#xff0c;目前是独立的开源项目&#xff0c;现在最常见的Kubernetes容器管理系统中&#xff0c;通常也会搭配Prometheus进行监控。prome…

模板:pb_ds指南

科技改变生活 前言 本来一直被畏于巨长的声明&#xff0c;没有学这个东西… 直到 棘手的操作 这道题&#xff0c;pb_ds模拟实现的两个log的做法不仅好写的一批&#xff0c;连时间竟然也把我单log的左偏树爆踩了&#xff1f;&#xff1f;&#xff1f; … 我选择打不过就加入… …

【学习笔记】多重背包相关优化——二进制优化/单调队列优化

多重背包——二进制优化/单调队列优化二进制优化单调队列优化代码都是 POJ1742 的&#xff0c;注意&#xff0c;那道题二进制优化会超时。 普通的多重背包式子&#xff0c;物品个数限制&#xff1a;c[i]c[i]c[i]&#xff0c;单个物品价值 w[i]w[i]w[i]&#xff0c;每个物品的体…