.NET Core中間件與依賴注入的一些思考

1.起源?
為什么會(huì)有這篇文章呢? 源于我看了老A的aspnet core 3 框架揭秘[1]?請(qǐng)求管道?篇產(chǎn)生的疑惑?
三點(diǎn)疑惑:
Singleton服務(wù)中注入Scoped服務(wù)產(chǎn)生內(nèi)存泄露? 關(guān)于中間件的生命周期是Singleton的? 怎么避免中間件、Singleton服務(wù)中使用Scoped服務(wù)不產(chǎn)生內(nèi)存泄漏?
2.知識(shí)面覆蓋
示例中會(huì)覆蓋到aspnet core相關(guān)的配置、依賴注入(周期)、中間件的知識(shí)點(diǎn),若不清楚的需要先看看這些概念以及基本使用。
收獲:和我一起帶著以上三個(gè)問題來進(jìn)行驗(yàn)證也就會(huì)收獲到相關(guān)知識(shí)點(diǎn)。
3. 測(cè)試環(huán)境準(zhǔn)備
創(chuàng)建三個(gè)服務(wù):
1. IOrderAppService(singleton)
2. IProductAppService(scoped)
3. ITransientTestAppService(transient)
創(chuàng)建請(qǐng)求控制器:
public?class?ProductController?:?Microsoft.AspNetCore.Mvc.Controller
{
????private?int?time?=?1;
????private?readonly?IHostApplicationLifetime?_lifetime;
????public?ProductController(IProductAppService?productAppService1,
????????IProductAppService?productAppService2,
????????IOrderAppService?orderAppService1,
????????IOrderAppService?orderAppService2,
????????ITransientTestAppService?transientTestAppService1,
????????ITransientTestAppService?transientTestAppService2,
????????IHostApplicationLifetime?lifetime)
????{
????????_lifetime?=?lifetime;
????}
????[HttpGet]
????[Route("/get")]
????public?Task<string>?Get()
????{
????????return?Task.FromResult($"第{time++}次請(qǐng)求成功!");
????}
????[HttpGet]
????[Route("/stop")]
????public?void?Stop()?=>?_lifetime.StopApplication();
}
創(chuàng)建中間件:
public?sealed?class?UrlMiddleware
{
????private?int?times?=?1;
????private?readonly?RequestDelegate?_next;
????public?UrlMiddleware(RequestDelegate?next,
????????IProductAppService?productAppService,
????????ITransientTestAppService?transientTestAppService)
????{
????????//構(gòu)造中的productAppService服務(wù)是由IApplicationBuilder.ApplicationServices根容器創(chuàng)建的
????????_next?=?next;
????}
????public?async?Task?InvokeAsync(HttpContext?context,IProductAppService?productAppService,ITransientTestAppService?transientTestAppService)
????{
????????//invoke中的productAppService其實(shí)是context.RequestServices子容器創(chuàng)建的。
????????//這里的context.RequestServices子容器也是由IApplicationBuilder.ApplicationServices根容器創(chuàng)建來的。
????????var?productService?=?context.RequestServices.GetRequiredService();//使用解析的方式和上面方法中注入進(jìn)來是一樣的作用,切記是使用子容器RequestServices解析
????????Console.WriteLine($"請(qǐng)求第{times++}次進(jìn)入U(xiǎn)rlMiddleware中間件。hash:{this.GetHashCode()}");
????????await?_next(context);
????}
}
注冊(cè)服務(wù):
service.AddTransient()
???????.AddScoped()
???????.AddSingleton();
這里若使用的
IMiddleware創(chuàng)建中間件也記得需要注冊(cè)。
4.開始驗(yàn)證
4.1 關(guān)于中間件的生命周期是Singleton的?
這里我們先驗(yàn)證下這個(gè)問題。為第一個(gè)問題做鋪墊。
文章中我就不做過多的代碼介紹,主要是對(duì)代碼片段的解釋,有需要的可以看源代碼[2]
開始運(yùn)行:
dotnet?run會(huì)注意到中間件構(gòu)造中注入的服務(wù)會(huì)在項(xiàng)目啟動(dòng)完成前就會(huì)創(chuàng)建完成。

開始請(qǐng)求:
輸入http://localhost:5002/get, 這是因?yàn)榕渲昧?
UseUrls,也可以直接使用UseSetting("urls"")。使用
UseSetting的key默認(rèn)定義在WebHostDefaults和HostDefaults中為了驗(yàn)證問題我們請(qǐng)求兩次。

開始請(qǐng)求 
中間件是否是單例 分析總結(jié):從兩次請(qǐng)求中可以確定不管是強(qiáng)類型的中間件還是按照約定(弱類型)的中間件都是單例的(Singleton)
這里穿插一下關(guān)于Singleton\Scoped\Transient生命周期控制臺(tái)輸出:
分析總結(jié):Scoped服務(wù)請(qǐng)求中只會(huì)創(chuàng)建一次并且請(qǐng)求完成后釋放 Transient服務(wù)每一次都會(huì)重新創(chuàng)建并且請(qǐng)求完成后全部釋放 Singleton整個(gè)應(yīng)用程序周期內(nèi)只會(huì)創(chuàng)建一次并且直到應(yīng)用程序關(guān)閉時(shí)才會(huì)釋放(慎用慎選擇)
4.2 Singleton服務(wù)中注入Scoped服務(wù)產(chǎn)生內(nèi)存泄露?
調(diào)用http://localhost:5002/stop?進(jìn)行遠(yuǎn)程關(guān)閉應(yīng)用程序。控制臺(tái)輸出:
分析總結(jié):中間件構(gòu)造中注入scoped服務(wù)時(shí)會(huì)跟隨根容器的釋放才會(huì)釋放,相當(dāng)于說就是會(huì)在整個(gè)應(yīng)用程序生命周期中存在,所以也就容易導(dǎo)致內(nèi)存泄漏。
從這里還沒能表現(xiàn)出構(gòu)造中的服務(wù)和invoke方法中的服務(wù)區(qū)別。。。下面進(jìn)行驗(yàn)證:
分析總結(jié):
從圖中畫線中能看出請(qǐng)求完成后只有invoke方法中的scoped\transient服務(wù)釋放了,中間件構(gòu)造中的任何類型服務(wù)都不會(huì)得到釋放,所以需要在中間件使用
關(guān)于非singleten服務(wù)時(shí)在方法中進(jìn)行注入,不要使用構(gòu)造注入,這是為什么呢?
其實(shí)invoke方法中的服務(wù)是通過子容器(context.RequestServices)創(chuàng)建而來的,所以跟隨請(qǐng)求完成子容器釋放也就會(huì)釋放掉子容器內(nèi)創(chuàng)建出的服務(wù)。
context.RequestServices是由IApplicationBuilder.ApplicationServices根容器創(chuàng)建而來的。
以上內(nèi)容也只是使用中間件這種特殊來進(jìn)行了測(cè)試,那么怎么來驗(yàn)證Singleton服務(wù)中注入scoped來進(jìn)行驗(yàn)證呢?自行嘗試?應(yīng)該是不可以的哦?`ServiceProviderOptions`。
4.3 怎么避免中間件、Singleton服務(wù)中使用Scoped服務(wù)不產(chǎn)生內(nèi)存泄漏?
其實(shí)4.2中已經(jīng)有了答案了。
如何避免?
在中間件中使用invoke方法中注入服務(wù)或者使用context.RequestServices.GetRequiredService<>();來解析服務(wù),不推薦(反模式)。
在singleton服務(wù)中使用使用IServiceProvider來創(chuàng)建子容器解析。
要是以上內(nèi)容有什么不對(duì)的地方歡迎也希望得到指點(diǎn)。
5 總結(jié)
從自己看書到自己寫代碼來驗(yàn)證以及寫這篇文章多多少少算花了兩天的時(shí)間,但是感覺還是有收獲的,算是搞清楚了一些問題。
強(qiáng)烈推薦老A的?aspnet core 3 框架揭秘[3]?,對(duì)深入aspnet core有很大的幫助,能 夠?qū)spnet core中的知識(shí)點(diǎn)有一個(gè)大體輪廓。
【推薦】.NET Core開發(fā)實(shí)戰(zhàn)視頻課程?★★★
.NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門篇-開篇及總體規(guī)劃
【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開篇及目錄索引
Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)
.NET Core中的一個(gè)接口多種實(shí)現(xiàn)的依賴注入與動(dòng)態(tài)選擇看這篇就夠了
10個(gè)小技巧助您寫出高性能的ASP.NET Core代碼
用abp vNext快速開發(fā)Quartz.NET定時(shí)任務(wù)管理界面
在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度
現(xiàn)身說法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化
