.NET Core中間件與依賴注入的一些思考
點擊上方藍字"小黑在哪里"關(guān)注我吧
1.起源?
為什么會有這篇文章呢? 源于我看了老A的aspnet core 3 框架揭秘[1] 請求管道 篇產(chǎn)生的疑惑?
三點疑惑:
Singleton服務(wù)中注入Scoped服務(wù)產(chǎn)生內(nèi)存泄露? 關(guān)于中間件的生命周期是Singleton的? 怎么避免中間件、Singleton服務(wù)中使用Scoped服務(wù)不產(chǎn)生內(nèi)存泄漏?
2.知識面覆蓋
示例中會覆蓋到aspnet core相關(guān)的配置、依賴注入(周期)、中間件的知識點,若不清楚的需要先看看這些概念以及基本使用。
收獲:和我一起帶著以上三個問題來進行驗證也就會收獲到相關(guān)知識點。
3. 測試環(huán)境準(zhǔn)備
創(chuàng)建三個服務(wù):
1. IOrderAppService(singleton)
2. IProductAppService(scoped)
3. ITransientTestAppService(transient)
創(chuà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++}次請求成功!");
????}
????[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其實是context.RequestServices子容器創(chuàng)建的。
????????//這里的context.RequestServices子容器也是由IApplicationBuilder.ApplicationServices根容器創(chuàng)建來的。
????????var?productService?=?context.RequestServices.GetRequiredService();//使用解析的方式和上面方法中注入進來是一樣的作用,切記是使用子容器RequestServices解析
????????Console.WriteLine($"請求第{times++}次進入UrlMiddleware中間件。hash:{this.GetHashCode()}");
????????await?_next(context);
????}
}
注冊服務(wù):
service.AddTransient()
???????.AddScoped()
???????.AddSingleton();
這里若使用的
IMiddleware創(chuàng)建中間件也記得需要注冊。
4.開始驗證
4.1 關(guān)于中間件的生命周期是Singleton的?
這里我們先驗證下這個問題。為第一個問題做鋪墊。
文章中我就不做過多的代碼介紹,主要是對代碼片段的解釋,有需要的可以看源代碼[2]
開始運行:
dotnet?run會注意到中間件構(gòu)造中注入的服務(wù)會在項目啟動完成前就會創(chuàng)建完成。

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

開始請求 
中間件是否是單例 分析總結(jié):從兩次請求中可以確定不管是強類型的中間件還是按照約定(弱類型)的中間件都是單例的(Singleton)
這里穿插一下關(guān)于Singleton\Scoped\Transient生命周期控制臺輸出:
分析總結(jié):Scoped服務(wù)請求中只會創(chuàng)建一次并且請求完成后釋放 Transient服務(wù)每一次都會重新創(chuàng)建并且請求完成后全部釋放 Singleton整個應(yīng)用程序周期內(nèi)只會創(chuàng)建一次并且直到應(yīng)用程序關(guān)閉時才會釋放(慎用慎選擇)
4.2 Singleton服務(wù)中注入Scoped服務(wù)產(chǎn)生內(nèi)存泄露?
調(diào)用http://localhost:5002/stop 進行遠(yuǎn)程關(guān)閉應(yīng)用程序。控制臺輸出:
分析總結(jié):中間件構(gòu)造中注入scoped服務(wù)時會跟隨根容器的釋放才會釋放,相當(dāng)于說就是會在整個應(yīng)用程序生命周期中存在,所以也就容易導(dǎo)致內(nèi)存泄漏。
從這里還沒能表現(xiàn)出構(gòu)造中的服務(wù)和invoke方法中的服務(wù)區(qū)別。。。下面進行驗證:
分析總結(jié):
從圖中畫線中能看出請求完成后只有invoke方法中的scoped\transient服務(wù)釋放了,中間件構(gòu)造中的任何類型服務(wù)都不會得到釋放,所以需要在中間件使用
關(guān)于非singleten服務(wù)時在方法中進行注入,不要使用構(gòu)造注入,這是為什么呢?
其實invoke方法中的服務(wù)是通過子容器(context.RequestServices)創(chuàng)建而來的,所以跟隨請求完成子容器釋放也就會釋放掉子容器內(nèi)創(chuàng)建出的服務(wù)。
context.RequestServices是由IApplicationBuilder.ApplicationServices根容器創(chuàng)建而來的。
以上內(nèi)容也只是使用中間件這種特殊來進行了測試,那么怎么來驗證Singleton服務(wù)中注入scoped來進行驗證呢?自行嘗試?應(yīng)該是不可以的哦?`ServiceProviderOptions`。
4.3 怎么避免中間件、Singleton服務(wù)中使用Scoped服務(wù)不產(chǎn)生內(nèi)存泄漏?
其實4.2中已經(jīng)有了答案了。
如何避免?
在中間件中使用invoke方法中注入服務(wù)或者使用context.RequestServices.GetRequiredService<>();來解析服務(wù),不推薦(反模式)。
在singleton服務(wù)中使用使用IServiceProvider來創(chuàng)建子容器解析。
要是以上內(nèi)容有什么不對的地方歡迎也希望得到指點。
5 總結(jié)
從自己看書到自己寫代碼來驗證以及寫這篇文章多多少少算花了兩天的時間,但是感覺還是有收獲的,算是搞清楚了一些問題。
強烈推薦老A的 aspnet core 3 框架揭秘[3] ,對深入aspnet core有很大的幫助,能 夠?qū)spnet core中的知識點有一個大體輪廓。
參考資料
aspnet core 3 框架揭秘: https://www.cnblogs.com/artech/
[2]源代碼: https://github.com/jonny-xhl/my-demo/tree/master/src/middleware/Jonny.AllDemo.SingleMiddleware
[3]aspnet core 3 框架揭秘: https://www.cnblogs.com/artech/
如果本文對您有用,
不妨點個“在看”或者轉(zhuǎn)發(fā)朋友圈支持一下
