Autofac 实例范围
实例范围决定了同一个服务的请求之间如何共享一个实例。请注意,你应该了解 生命周期范围的概念 ,以便更好地理解这里发生的事情。
当请求一个服务时,Autofac 可以返回一个单独的实例(单例范围)、一个新的实例(每个依赖范围)或在一个上下文中(如线程或 HTTP 请求)中的单个实例(按生命周期范围)。
这同样适用于从显式 Resolve()
调用返回的实例,以及容器内部创建的实例,以满足其他组件的依赖。
注意 选择正确的生命周期范围将有助于避免 被囚禁的依赖 以及其他组件生存时间过长或过短的陷阱。开发者需要为应用程序的每个组件做出正确的选择。
按依赖范围实例化
在其他容器中也称为 “瞬态” 或 “工厂” 。使用按依赖范围的实例化,对服务的每次请求都会返回一个唯一的实例。
这是默认设置,如果没有指定其他选项。
var builder = new ContainerBuilder();
// 这...
builder.RegisterType<Worker>();
// ...等同于这个:
builder.RegisterType<Worker>().InstancePerDependency();
当你为按依赖范围的组件进行解析时,每次都会得到一个新的实例。
using(var scope = container.BeginLifetimeScope())
{
for(var i = 0; i < 100; i++)
{
// 这个循环中的 100 个 Worker 实例
// 在每次调用 `Resolve<Worker>()` 时都是全新的。
var w = scope.Resolve<Worker>();
w.DoWork();
}
}
单例实例
这也被称为 “单例” 。使用单例范围,所有根级和嵌套范围内的请求都将返回一个实例。
var builder = new ContainerBuilder();
builder.RegisterType<Worker>().SingleInstance();
当你解析单例组件时,无论在哪里请求,你总是会得到相同的实例。
// 通常不建议直接从容器中解析东西,但为了单例演示目的...
var root = container.Resolve<Worker>();
// 你可以从任何嵌套的生命周期范围中多次解析该工人,
// 无论是在哪个范围,root、w1 和 w2 总是同一个对象实例。
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>();
}
}
}
按生命周期范围实例化
此范围适用于嵌套的生命周期。具有按生命周期范围实例化的组件最多在每个嵌套生命周期范围内有一个实例。
这对于特定于单个工作单元的对象很有用,这些对象可能需要嵌套更多的逻辑工作单元。每个嵌套生命周期范围将获得注册依赖项的一个新实例。
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>();
}
}
using(var scope3 = container.BeginLifetimeScope())
{
var w3 = scope3.Resolve<Worker>();
using(var scope4 = scope3.BeginLifetimeScope())
{
// w3 和 w4 不同,因为它们来自不同的范围。
var w4 = scope4.Resolve<Worker>();
}
}
// 容器本身就是生命周期范围!如果你从容器中解析一个按生命周期范围的实例,
// 那么这个实例将存活到容器的整个生命周期,并且实际上是一个单例。
// 如果有其他东西尝试从容器中解析 Worker,它将在整个容器的生命周期内被保留。
var w5 = container.Resolve<Worker>();
using(var scope5 = container.BeginLifetimeScope())
{
// w5 和 w6 不同。
var w6 = scope5.Resolve<Worker>();
}
按匹配生命周期范围实例化
这类似于上面的“按生命周期范围实例化”的概念,但提供了更精确的实例共享控制。
当你创建一个嵌套生命周期范围时,你可以 “标记” 或 “命名” 范围。具有按匹配生命周期范围的组件最多在一个具有给定名称的嵌套生命周期范围内有一个实例。 这允许你在没有全局共享实例的情况下创建一种 “按范围的单例” ,其他嵌套生命周期范围可以共享组件实例。
这对于特定于单个工作单元的对象很有用,例如 HTTP 请求,因为可以为每个工作单元创建一个嵌套生命周期。如果为每个 HTTP 请求创建了一个嵌套生命周期,那么任何具有按生命周期范围的组件都将有一个 HTTP 请求的实例。(关于按请求生命周期范围的更多信息,请参阅下面。)
在大多数应用中,只需要一层容器嵌套就足以表示工作的范围。如果需要更多的嵌套层次(例如:全局 > 请求 > 事务),可以在层次结构中的特定级别配置组件共享,使用标签。
var builder = new ContainerBuilder();
builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("my-request");
提供的标签值与您启动范围时关联的生命周期范围相关联。如果你试图解析一个按匹配生命周期范围的组件,而没有匹配名称的正确范围,你会收到一个异常。
// 使用标签创建生命周期范围。
using(var scope1 = container.BeginLifetimeScope("my-request"))
{
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("my-request"))
{
for(var i = 0; i < 100; i++)
{
// w3 将与之前带有标签的生命周期范围中的 Worker 实例不同。
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>();
}
按请求实例化
某些应用类型自然适合 “请求” 类型的语义,例如 ASP.NET 的 Web Forms 和 MVC 应用。在这种应用类型中,能够有一个 “每个请求的单例” 很有帮助。
按请求实例化建立在按匹配生命周期范围实例化之上,提供了一个众所周知的生命周期范围标签、注册便利方法和常见应用类型的集成。但背后还是按匹配生命周期范围实例化。
这意味着如果你尝试解析注册为按请求实例化的组件,但是没有当前的请求... 你会收到一个异常。
var builder = new ContainerBuilder();
builder.RegisterType<Worker>().InstancePerRequest();
ASP.NET Core 使用按生命周期范围实例化而不是按请求实例化。有关更多信息,请参阅 ASP.NET Core 集成文档 。
按所有者实例化
所有者<T> 隐式关系类型 创建新的嵌套生命周期范围。可以使用按所有者实例化注册来将依赖项绑定到所有者实例上。
var builder = new ContainerBuilder();
builder.RegisterType<MessageHandler>();
builder.RegisterType<ServiceForHandler>().InstancePerOwned<MessageHandler>();
在这个例子中,ServiceForHandler
服务将绑定到所有者的生命周期 MessageHandler
实例上。
using(var scope = container.BeginLifetimeScope())
{
// MessageHandler 实例以及通过它解析的 ServiceForHandler 服务都在其下的 "scope" 子生命周期内。请注意,解决所有者类型 `Owned<T>` 意味着你负责其释放。
var h1 = scope.Resolve<Owned<MessageHandler>>();
h1.Dispose();
}
线程范围
Autofac 可以强制执行一个线程绑定的对象不会满足另一个线程绑定组件的依赖项。虽然没有便捷的方法实现这一点,但你可以使用生命周期范围来实现。
var builder = new ContainerBuilder();
builder.RegisterType<MyThreadScopedComponent>()
.InstancePerLifetimeScope();
var container = builder.Build();
然后,每个线程都有自己的生命周期范围:
void ThreadStart()
{
using (var threadLifetime = container.BeginLifetimeScope())
{
var thisThreadsInstance = threadLifetime.Resolve<MyThreadScopedComponent>();
}
}
重要提示:考虑到多线程场景,你需要非常小心,确保父范围不会在创建的子线程下被释放。 如果你在创建线程后释放父范围,可能会导致组件无法解析。
通过 ThreadStart()
执行的每个线程都会获得 MyThreadScopedComponent
的一个实例——这本质上是生命周期范围内的单例。由于范围实例永远不会提供给外部范围,因此更容易保持线程组件的隔离。
你可以在创建线程的代码中注入父生命周期范围,通过参数接收 ILifetimeScope
。Autofac 知道自动注入当前生命周期范围,并可以从此创建嵌套范围。
public class ThreadCreator
{
private ILifetimeScope _parentScope;
public ThreadCreator(ILifetimeScope parentScope)
{
this._parentScope = parentScope;
}
public void ThreadStart()
{
using (var threadLifetime = this._parentScope.BeginLifetimeScope())
{
var thisThreadsInstance = threadLifetime.Resolve<MyThreadScopedComponent>();
}
}
}
如果你想更严格地限制这个规则,可以使用与匹配生命周期范围关联的实例(如上所述),将线程范围组件与内部生命周期关联起来(它们仍然会从外部容器中的工厂/单例组件接收依赖项)。这种方法的效果如下:
图中的 “上下文” 指的是使用 BeginLifetimeScope()
创建的容器。