前言
上一小节简单阐述了依赖注入及Asp.NetCore中自带依赖注入组件的常规用法,其中提到容器管控了自己创建对象的生命周期,包含了三种生命周期:Singleton、Scoped、Transient, 对于Singleton、Transient相对于Scoped来说比较好理解,其实这里面有一个作用域的概念,也可以理解为根容器和子容器的范围;上一小节中有一个例子中说到,当注入的生命周期为Scoped的时,在同一个请求内,注入的对象都是同一个,这里Asp.NetCore将每个请求作为了一个作用域,在此作用域内,生命周期为Scoped的对象就是同一个;下面简单说说作用域和对象释放的常规知识点;
正文
作用域这里可以理解为服务范围,由IServiceScope承载,如其源代码所示,每一个服务范围内都一个根容器,如下所示:
// 摘要:
// /// The System.IDisposable.Dispose method ends the scope lifetime. Once Dispose
// /// is called, any scoped services that have been resolved from /// Microsoft.Extensions.DependencyInjection.IServiceScope.ServiceProvider
// will be /// disposed. ///
public interface IServiceScope : Object, IDisposable
{
// IServiceProvider 即代表容器
//
// 摘要:
// /// The System.IServiceProvider used to resolve dependencies from the scope.
// ///
IServiceProvider ServiceProvider
{
get;
}
}
每一个服务范围内可以通过自身IServiceProvider创建对应的子作用域,而任何一个子作用域的IServiceProvider都有对根容器的引用,如下图结构:
每一个IServiceProvider中都会将其创建的对象存入列表中,针对于继承与IDisposable接口的类型对象会单独存放在一个列表中,当作用域IServiceScope对象的Dispose方法被调用时,最终对应IServicePorvider对应的对象列表也会清空,针对于继承与IDisposable类型的对象会调用其Dispose方法;最后导致对应作用域下容器创建的对象成为垃圾对象,被GC给回收;
在Asp.NetCore中,框架默认将其分有根作用域(与引用程序同生命周期)和请求作用域(每一个请求一个作用域),通常也会称其为根容器和请求容器,名称分别为ApplicationServices和RequestServices,在程序中获取方式如下:
- 通过IApplicationBuilder对象可以获取ApplicationServices
- 通过HttpContext.RequestServices获取RequestServices
作用域简单举例,在请求作用域下再创建子作用域,分别查看对应不同生命周期对象是否一致,新建一个WebApi项目,针对三个生命周期添加了对应文件,结构如下:
对应红框内的文件内容如下:
细心的小伙伴可能会看到,每个实现类里面都继承了IDisposable接口,这主要是后面显示释放用的,这里先不管;编辑好以上内容之后就将其进行注册,如下:
接下来就是使用了,这里通过这Action中使用,在当前的请求作用域下创建子作用域,对比每一个生命周期对象,如代码所示:
[HttpGet]
public string Get([FromServices]ITestSingleton testSingleton, [FromServices]ITestSingleton testSingleton1,
[FromServices]ITestScoped testScoped, [FromServices]ITestScoped testScoped1,
[FromServices]ITestTransient testTransient, [FromServices]ITestTransient testTransient1)
{
//获取请求作用域(请求容器)
var requestServices = HttpContext.RequestServices;
//在请求作用域下创建子作用域
using(IServiceScope scope = requestServices.CreateScope())
{
//在子作用域中通过其容器获取注入的不同生命周期对象
ITestSingleton testSingleton11 = scope.ServiceProvider.GetService<ITestSingleton>();
ITestScoped testScoped11 = scope.ServiceProvider.GetService<ITestScoped>();
ITestTransient testTransient11 = scope.ServiceProvider.GetService<ITestTransient>();
ITestSingleton testSingleton12 = scope.ServiceProvider.GetService<ITestSingleton>();
ITestScoped testScoped12 = scope.ServiceProvider.GetService<ITestScoped>();
ITestTransient testTransient12 = scope.ServiceProvider.GetService<ITestTransient>();
Console.WriteLine("================Singleton=============");
Console.WriteLine($"请求作用域的ITestSingleton对象:{testSingleton.GetHashCode()}");
Console.WriteLine($"请求作用域的ITestSingleton1对象:{testSingleton1.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestSingleton11对象:{testSingleton11.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestSingleton12对象:{testSingleton12.GetHashCode()}");
Console.WriteLine("================Scoped=============");
Console.WriteLine($"请求作用域的ITestScoped对象:{testScoped.GetHashCode()}");
Console.WriteLine($"请求作用域的ITestScoped1对象:{testScoped1.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestScoped11对象:{testScoped11.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestScoped12对象:{testScoped12.GetHashCode()}");
Console.WriteLine("================Transient=============");
Console.WriteLine($"请求作用域的ITestTransient对象:{testTransient.GetHashCode()}");
Console.WriteLine($"请求作用域的ITestTransient1对象:{testTransient1.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestTransient11对象:{testTransient11.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestTransient12对象:{testTransient12.GetHashCode()}");
}
return "TestServiceScope";
}
运行,进行请求,看打印结果:
- 对于Singleton来说始终不变,因为其是跟随根容器生命周期,引用程序退出才释放;
- 对于Scoped来说只要在自己的作用域内就是单例的;
- 对于Transient来说始终创建;
以上一直在说释放,下面利用继承IDisposable接口释放时会调用对应Dispoable方法的原理简单演示各个生命周期的释放时机;在Controller中增加几个Action方法,如下:
这里使用IHostApplicationLifetime中的StopApplication模拟关闭程序释放单例下的对象;运行看效果:
使用坑:不要从根容器中获取Transient生命周期的对象,因为通过根容器创建的对象不会回收,除非等到应用程序退出,这样会导致内存泄露;如下演示:
新增Action方法:
运行,发送请求看结果:
总结
作用域及对象释放就简单说这么多,容器只管理自己创建出来的对象生命周期;下一节说说使用第三方组件扩展依赖注入功能;