.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) 这里穿插一下关于SingletonScopedTransient生命周期控制台输出:
分析总结:
- Scoped服务请求中只会创建一次并且请求完成后释放
- Transient服务每一次都会重新创建并且请求完成后全部释放
- Singleton整个应用程序周期内只会创建一次并且直到应用程序关闭时才会释放(慎用慎选择)
4.2 Singleton服务中注入Scoped服务产生内存泄露?
调用http://localhost:5002/stop 进行远程关闭应用程序。控制台输出:
分析总结:中间件构造中注入scoped服务时会跟随根容器的释放才会释放,相当于说就是会在整个应用程序生命周期中存在,所以也就容易导致内存泄漏。
从这里还没能表现出构造中的服务和invoke方法中的服务区别。。。下面进行验证:
分析总结:
从图中画线中能看出请求完成后只有invoke方法中的scopedtransient服务释放了,中间件构造中的任何类型服务都不会得到释放,所以需要在中间件使用
关于非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/
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- JSP开发基础入门学习1
- codeforces 1407C(数学+交互题)
- codeforces 1420C1
- codeforces 1417D(思维,构造)
- Servlet基础入门学习2
- codeforces 1426D(思维)
- codeforces 1324E(dp)
- OpenCV4.4 CUDA编译与加速全解析
- codeforces 1077D(二分)
- codeforces 1077F1(dp)
- Servlet基础入门学习1
- Lombok,你的开发效率神器!
- codeforces 1272E(反向建边+多源bfs)
- Tomcat在Java开发中的使用笔记
- codeforces 1423K(数学+差分数组预处理)