.NET Core中间件与依赖注入的一些思考
點擊上方藍字"小黑在哪里"關注我吧
1.起源?
為什么會有這篇文章呢? 源于我看了老A的aspnet core 3 框架揭秘[1] 請求管道 篇產生的疑惑?
三點疑惑:
Singleton服務中注入Scoped服務產生內存泄露?
關于中間件的生命周期是Singleton的?
怎么避免中間件、Singleton服務中使用Scoped服務不產生內存泄漏?
2.知識面覆蓋
示例中會覆蓋到aspnet core相關的配置、依賴注入(周期)、中間件的知識點,若不清楚的需要先看看這些概念以及基本使用。
收獲:和我一起帶著以上三個問題來進行驗證也就會收獲到相關知識點。
3. 測試環境準備
創建三個服務:
1. IOrderAppService(singleton) 2. IProductAppService(scoped) 3. ITransientTestAppService(transient)創建請求控制器:
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(); }創建中間件:
public?sealed?class?UrlMiddleware {private?int?times?=?1;private?readonly?RequestDelegate?_next;public?UrlMiddleware(RequestDelegate?next,IProductAppService?productAppService,ITransientTestAppService?transientTestAppService){//構造中的productAppService服務是由IApplicationBuilder.ApplicationServices根容器創建的_next?=?next;}public?async?Task?InvokeAsync(HttpContext?context,IProductAppService?productAppService,ITransientTestAppService?transientTestAppService){//invoke中的productAppService其實是context.RequestServices子容器創建的。//這里的context.RequestServices子容器也是由IApplicationBuilder.ApplicationServices根容器創建來的。var?productService?=?context.RequestServices.GetRequiredService<IProductAppService>();//使用解析的方式和上面方法中注入進來是一樣的作用,切記是使用子容器RequestServices解析Console.WriteLine($"請求第{times++}次進入UrlMiddleware中間件。hash:{this.GetHashCode()}");await?_next(context);} }注冊服務:
service.AddTransient<ITransientTestAppService,?TransientTestAppService>().AddScoped<IProductAppService,?ProductAppService>().AddSingleton<IOrderAppService,?OrderAppService>();這里若使用的IMiddleware創建中間件也記得需要注冊。
4.開始驗證
4.1 關于中間件的生命周期是Singleton的?
這里我們先驗證下這個問題。為第一個問題做鋪墊。
文章中我就不做過多的代碼介紹,主要是對代碼片段的解釋,有需要的可以看源代碼[2]
開始運行:
dotnet?run會注意到中間件構造中注入的服務會在項目啟動完成前就會創建完成。
開始請求:
輸入http://localhost:5002/get, 這是因為配置了 UseUrls,也可以直接使用UseSetting("urls"")。
使用UseSetting的key默認定義在WebHostDefaults和HostDefaults中
為了驗證問題我們請求兩次。
開始請求中間件是否是單例分析總結:從兩次請求中可以確定不管是強類型的中間件還是按照約定(弱類型)的中間件都是單例的(Singleton)
這里穿插一下關于Singleton\Scoped\Transient生命周期控制臺輸出:分析總結:
Scoped服務請求中只會創建一次并且請求完成后釋放
Transient服務每一次都會重新創建并且請求完成后全部釋放
Singleton整個應用程序周期內只會創建一次并且直到應用程序關閉時才會釋放(慎用慎選擇)
4.2 Singleton服務中注入Scoped服務產生內存泄露?
調用http://localhost:5002/stop 進行遠程關閉應用程序。控制臺輸出:分析總結:中間件構造中注入scoped服務時會跟隨根容器的釋放才會釋放,相當于說就是會在整個應用程序生命周期中存在,所以也就容易導致內存泄漏。
從這里還沒能表現出構造中的服務和invoke方法中的服務區別。。。下面進行驗證:
分析總結:
從圖中畫線中能看出請求完成后只有invoke方法中的scoped\transient服務釋放了,中間件構造中的任何類型服務都不會得到釋放,所以需要在中間件使用 關于非singleten服務時在方法中進行注入,不要使用構造注入,這是為什么呢?其實invoke方法中的服務是通過子容器(context.RequestServices)創建而來的,所以跟隨請求完成子容器釋放也就會釋放掉子容器內創建出的服務。 context.RequestServices是由IApplicationBuilder.ApplicationServices根容器創建而來的。以上內容也只是使用中間件這種特殊來進行了測試,那么怎么來驗證Singleton服務中注入scoped來進行驗證呢?自行嘗試?應該是不可以的哦?`ServiceProviderOptions`。4.3 怎么避免中間件、Singleton服務中使用Scoped服務不產生內存泄漏?
其實4.2中已經有了答案了。
如何避免?
在中間件中使用invoke方法中注入服務或者使用context.RequestServices.GetRequiredService<>();來解析服務,不推薦(反模式)。
在singleton服務中使用使用IServiceProvider來創建子容器解析。
要是以上內容有什么不對的地方歡迎也希望得到指點。
5 總結
從自己看書到自己寫代碼來驗證以及寫這篇文章多多少少算花了兩天的時間,但是感覺還是有收獲的,算是搞清楚了一些問題。
強烈推薦老A的 aspnet core 3 框架揭秘[3] ,對深入aspnet core有很大的幫助,能 夠對aspnet core中的知識點有一個大體輪廓。
參考資料
[1]
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/
如果本文對您有用,
不妨點個“在看”或者轉發朋友圈支持一下
總結
以上是生活随笔為你收集整理的.NET Core中间件与依赖注入的一些思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 收好这张MySQL导图,全是知识点!
- 下一篇: C# 中 System.Index 结构