我在 Autofac 中发现了一些与通过委托工厂从单例服务中解析范围服务相关的意外行为。以下行为是预期的还是我误解了什么?
为了避免强制依赖问题,我们不应该有一个将范围服务作为依赖项的单例服务。这是完全有道理的。但是,如果我们使用 Autofac 的委托工厂(即
Func<T>
注入),我希望通过工厂解析的依赖项具有配置的生命周期。相反,它始终具有单例生命周期。
为了使事情具体化,给出以下两个类:
// a dependency that will be registered as scoped.
interface IMyScopedService
{
Guid InstanceId { get; }
}
class MyScopedService : IMyScopedService
{
public Guid InstanceId { get; } = Guid.NewGuid();
}
// our top-level singleton service, it has a Func<T> dependency on the above scoped service
interface IMySingletonService
{
void PrintUsingScopedDependency();
}
class MySingletonService : IMySingletonService
{
private readonly Func<IMyScopedService> myScopedService;
public MySingletonService(Func<IMyScopedService> myScopedService)
{
this.myScopedService = myScopedService;
}
public void PrintUsingScopedDependency() =>
// invoke the delegate factory to retrieve an instance of our dependency.
Console.WriteLine(myScopedService().InstanceId);
}
以及 .NET 8 程序中的以下用法:
using Autofac;
// container setup with a singleton service that depends on a scoped service
var builder = new ContainerBuilder();
builder.RegisterType<MySingletonService>().As<IMySingletonService>().SingleInstance();
builder.RegisterType<MyScopedService>().As<IMyScopedService>().InstancePerLifetimeScope();
var container = builder.Build();
// print using a method that invokes the Func<> to get the scoped service
using (var scope1 = container.BeginLifetimeScope())
{
var singleton = scope1.Resolve<IMySingletonService>();
// should print the same guid twice because it's the same scope. This works.
singleton.PrintUsingScopedDependency();
singleton.PrintUsingScopedDependency();
}
// same as above, but a different scope
using (var scope2 = container.BeginLifetimeScope())
{
var singleton = scope2.Resolve<IMySingletonService>();
// should print another Guid twice
// HOWEVER -- it prints the same Guid as the first scope
singleton.PrintUsingScopedDependency();
singleton.PrintUsingScopedDependency();
}
我希望程序打印两个不同的指南,每个指南打印两次:
84b369d6-43bb-48ce-8c64-66d90e4d64d4
84b369d6-43bb-48ce-8c64-66d90e4d64d4
63011a0f-5cc2-4566-a9ae-0d6b35e1ed6e
63011a0f-5cc2-4566-a9ae-0d6b35e1ed6e
相反,它会打印相同的指南 4 次:
84b369d6-43bb-48ce-8c64-66d90e4d64d4
84b369d6-43bb-48ce-8c64-66d90e4d64d4
84b369d6-43bb-48ce-8c64-66d90e4d64d4
84b369d6-43bb-48ce-8c64-66d90e4d64d4
委托工厂不使用配置的生命周期是否有原因?我理解强制依赖问题,但它似乎不应该适用于委托工厂。
该行为是预期的,因为任何单实例服务都将始终从“根范围”创建和解析,从那里看不到其他当前或未来的范围。此外,该服务可能具有的任何依赖项也必须从根解决。因此,您的工厂委托将从根解析并移交给IMySingletonService
,因此,工厂委托永远不会看到除根之外的任何其他范围。
scope1.Resolve<IMySingletonService>();
调用时,即使在 scope1
实例上进行 Resolve 调用,由于
IMySingletonService
已注册为单个实例,因此解析必须“爬升”到根范围,创建实例(因为它还不存在)并将其存储在那里,然后从 Resolve
调用返回该实例。接下来,当您制作scope2.Resolve<IMySingletonService>();
时,分辨率必须再次“爬升”到根范围。此时,IMySingletonService
实例(及其工厂委托实例)
已经存在于根范围中,因此该实例被抓取并从
Resolve
调用返回。
这相当简单,这就是 Autofac 确保“单实例”服务仅创建一次并且该实例将在根容器的整个生命周期中重复使用的方式。要认识到的一点是,现在由 IMySingletonService
拥有的工厂委托将只能从根解析实例,因为任何子作用域(
scope1
和
scope2
)对它来说都是不可见的。使用工厂从根解析 IMyScopedService
将创建 MyScopedService
的一个实例并将其存储在根范围中,从而有效地使其成为单个实例。