【Autofac笔记】生命周期作用域
概述
服务的生命周期
服务的生命周期是服务实例在程序中生存的时间。例如您“新建”一个实现IDisposable的对象,然后再对其进行调用Dispose()。
选择正确的生命周期范围将有助于避免组件寿命过长或不够长的俘获依赖和其他陷阱。 开发人员需要为每个应用程序组件做出正确的选择。
服务的范围(作用域)
服务范围是应用程序中可以与其他使用该服务的组件共享该服务的区域。当请求服务时,Autofac可以返回单个实例,新实例或某种上下文中的单个实例,例如 线程或HTTP请求(每个生命周期范围)。
Lifetime Scope(生命周期范围)
Autofac中生命周期范围的概念结合了这两个概念。实际上,生命周期范围等同于应用程序中的一个工作单元。一个工作单元可以在开始时开始一个生命周期范围,然后该工作单元所需的服务将从一个生命周期范围得到解析。当您解析服务时,Autofac跟踪已解析的一次性(可配置)组件。在工作单元结束时,您将释放相关的生命周期范围,Autofac将自动清理/释放解析后的服务。
务必始终从生命周期范围而不是根容器来解析服务。由于生命周期作用域的处置跟踪特性,如果您从容器(“根生命周期作用域”)中解析许多可丢弃组件,您可能会无意中给自己造成内存泄漏。根容器将一直保存对这些可丢弃组件的引用,直到其存在(通常是应用程序的生存期),因此它可以释放这些组件。
为什么不推荐从容器解析服务?
容器本身也是一个生命周期,你可以从容器解析服务,但是不推荐,因为容器在应用的生命周期内一直存在. 如果你直接从该容器中解析了太多东西, 应用结束时将会有一堆东西等着被释放,很有可能造成内存泄漏
因此应该从容器创建一个子生命周期,并从中解析,当解析完后释放子生命周期,解析的服务也一并清理了
获取根作用域:
var rootScope = ((Autofac.Core.Lifetime.LifetimeScope)lifetimeScore).RootLifetimeScope;
有哪些生命周期作用域
Instance Per Dependency
每次都会返回一个新的实例,并且这是默认的生命周期。
var builder = new ContainerBuilder();
builder.RegisterType<Worker>();
// 此句代码的效果同上
builder.RegisterType<Worker>().InstancePerDependency();
当您解析每个依赖项的实例组件时,每次都会得到一个新组件。
using(var scope = container.BeginLifetimeScope())
{
for(var i = 0; i < 100; i++)
{
// Every one of the 100 Worker instances
// resolved in this loop will be brand new.
var w = scope.Resolve<Worker>();
w.DoWork();
}
}
Single Instance
单例,所有服务请求都将会返回同一个实例。
var builder = new ContainerBuilder();
builder.RegisterType<Worker>().SingleInstance();
当您解析单个实例组件时,无论您请求何处,都始终获得相同的实例。
var root = container.Resolve<Worker>();
using(var scope1 = container.BeginLifetimeScope())
{
for(var i = 0; i < 100; i++)
{
var w1 = scope1.Resolve<Worker>();
using(var scope2 = scope1.BeginLifetimeScope())
{
var w2 = scope2.Resolve<Worker>();
// root, w1, and w2 are always literally the same object instance.
}
}
}
Instance Per Lifetime Scope
在一个嵌套语句块中,只会返回一个实例。
var builder = new ContainerBuilder();
builder.RegisterType<Worker>().InstancePerLifetimeScope();
在解决每个生命周期实例作用域组件时,每个嵌套作用域将获得一个实例(例如,每个工作单元)。
using(var scope1 = container.BeginLifetimeScope())
{
for(var i = 0; i < 100; i++)
{
// 每次从这里解析它,你会得到相同的实例。
var w1 = scope1.Resolve<Worker>();
}
}
using(var scope2 = container.BeginLifetimeScope())
{
for(var i = 0; i < 100; i++)
{
//每次解析都会得到一个同样的实例,但是这个示例和上面的循环的实例不是同一个
var w2 = scope2.Resolve<Worker>();
}
}
Instance Per Matching Lifetime Scope
这与上面的'每个生命周期的实例范围'概念类似,但允许更精确地控制实例共享。
当您创建嵌套的生存期范围时,您可以“标记”或“命名”范围。具有每匹配生命周期范围的组件每个嵌套生命周期范围最多只有一个实例与给定名称匹配。这允许您创建一种“范围单例”,其中嵌套的生命周期范围可以在不声明全局共享实例的情况下共享某个组件的实例。
这对于特定于单个工作单元的对象是有用的,例如,一个HTTP请求,作为一个嵌套的生命周期可以创建每个工作单元。如果每个HTTP请求都创建一个嵌套的生命周期,那么每个具有每个生命周期范围的组件都将为每个HTTP请求创建一个实例。 (有关每个请求生命周期范围的更多信息。)
在大多数应用中,只有一层容器嵌套足以代表工作单元的范围。如果需要更多级别的嵌套(例如像global-> request-> transaction这样的东西),组件可以配置为使用标签在层次结构中的特定级别共享。
var builder = new ContainerBuilder();
builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("myrequest");
提供的标记值在启动时与生存期范围关联。 如果在没有正确命名的生命周期范围时尝试解析每个匹配生命周期范围的组件,则会得到一个异常。
//使用标签创建生命周期
using(var scope1 = container.BeginLifetimeScope("myrequest"))
{
for(var i = 0; i < 100; i++)
{
var w1 = scope1.Resolve<Worker>();
using(var scope2 = scope1.BeginLifetimeScope())
{
var w2 = scope2.Resolve<Worker>();
// w1和w2始终是同一个对象
//实例,因为该组件是每个匹配生命周期范围的,
//所以它实际上是一个单例
//命名范围
}
}
}
//使用标签创建另一个生命周期作用域
using(var scope3 = container.BeginLifetimeScope("myrequest"))
{
for(var i = 0; i < 100; i++)
{
// w3 will be DIFFERENT than the worker resolved in the
// earlier tagged lifetime scope.
var w3 = scope3.Resolve<Worker>();
using(var scope4 = scope3.BeginLifetimeScope())
{
var w4 = scope4.Resolve<Worker>();
// w3和w4始终是同一个对象,因为
//他们在相同的标记范围内,但他们是
//与之前的w1,w2不一样。
}
}
}
//你无法解析每个匹配生命周期的组件
//如果没有匹配的范围。
using(var noTagScope = container.BeginLifetimeScope())
{
//因为这个范围没有,所以抛出一个异常
//有预期的标签,也没有任何父范围!
var fail = noTagScope.Resolve<Worker>();
}
Instance Per Request
某些应用程序类型自然适用于“请求”类型语义,例如ASP.NET Web Forms和MVC应用程序。 在这些应用程序类型中,有能力为每个请求提供一种“单例”。
通过提供众所周知的生命周期范围标记,注册便利方法以及针对常见应用程序类型的集成,每个请求的实例基于每个匹配生命周期范围的实例构建。 但在幕后,它仍然只是每个匹配生命周期范围的实例。
这意味着如果您尝试解析注册为每个请求实例但没有当前请求的组件,那么您将得到一个异常。
var builder = new ContainerBuilder();
builder.RegisterType<Worker>().InstancePerRequest();
Instance Per Owned?
实例生命周期事件
Autofac暴露了一些能在实例生命周期多个阶段拦截到的事件. 这些事件在组件注册时被订阅
- OnPreparing
- OnActivating
- OnActivated
- OnRelease
OnPreparing
OnPreparing
事件在一个新的组件实例被需要的时候被触发, 它在 OnActivating 之前被触发.
这个事件可以用于指定一组参数信息, Autofac在创建一个新的组件实例会用到这些参数.
这个事件的主要作用是模拟或拦截services, 那些Autofac在正常的组件激活时作为参数传入的services, 通过用自定义的参数给 PreparingEventArgs 的 Parameters 属性赋值.
在使用这个事件赋值参数时, 考虑下是否在注册时定义这些会更合适, 使用 parameter registration.
OnActivating
OnActivating
事件在组件被使用前触发. 你可以:
- 修改实例成另一个或者把实例包裹在代理对象中
- 进行属性注入或方法注入
- 进行其他的初始化工作
在一些情况下, 比如用 RegisterType
builder.RegisterType<TConcrete>() // FAILS: will throw at cast of TInterfaceSubclass to type TConcrete
.As<TInterface>()
.OnActivating(e => e.ReplaceInstance(new TInterfaceSubclass(e.Instance)));
简单的解决方案是通过两步完成注册:
builder.RegisterType<TConcrete>().AsSelf();
builder.Register<TInterface>(c => c.Resolve<TConcrete>())
.OnActivating(e => e.ReplaceInstance(new TInterfaceSubclass(e.Instance)));
OnActivated
OnActivated
事件在组件完全构造完成后触发. 你可以完成一些应用级别的需要基于组件已构建完成为前提的任务 - 这种情况较少.
OnRelease
OnRelease
在组件释放之后会被调用,OnRelease
事件替换了 原始的组件释放行为. 它的原始的组件释放行为会调用Dispose()
方法, OnRelease
用提供的实现方法替换这一释放行为.
释放
从生命周期作用域解析组件
生命周期作用域通过调用BeginLifetimeScope()
创建. 最简单的是在using
块中. 使用生命周期作用域来解析你的组件然后当工作单元完成后释放作用域.
using (var lifetime = container.BeginLifetimeScope())
{
var component = lifetime.Resolve<SomeComponent>();
}
Autofac’s ASP.NET MVC 集成, 一个生命周期作用域将会在web请求开始时被创建然后所有的组件将会从中解析. 在请求结束时, 作用域会被自动释放 - 你自己不需要额外创建作用域.
OnRelease()
如果你的组件不实现 IDisposable 但仍然需要在生命周期作用域的结尾完成一些释放工作, 你可以使用 释放生命周期事件.
var builder = new ContainerBuilder();
builder.RegisterType<SomeComponent>()
.OnRelease(instance => instance.CleanUp());
var container = builder.Build();
禁止释放
组件默认被容器拥有并且会在适当的时候被它释放掉. 如果要禁止这个行为:
builder.RegisterType<SomeComponent>().ExternallyOwned();
以这种方式注册的对象, 容器不会调用它的Dispose()
或DisposeAsync()
方法. 它何时释放取决于你
另一种方式:
在你的消费代码中并不是传入一个依赖T
, 而是一个依赖 Owned<T>
. 你的消费代码需要负责释放.
public class Consumer
{
private Owned<DisposableComponent> _service;
public Consumer(Owned<DisposableComponent> service)
{
_service = service;
}
public void DoWork()
{
_service.Value.DoSomething();
_service.Dispose();
}
}