Autofac多次注册组件

Squ*_*azz 4 .net c# dependency-injection ioc-container autofac

在上一个关于如何可视化依赖关系图的问题中,我获得了现在用来可视化我的依赖关系图的代码的基础,因为它是由 Autofac 解析的。

\n\n

运行代码,我得到一棵树,生成如下代码。

\n\n
Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController (3851,7 ms. / 0,0 ms.) Depth: 0\n   Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController (3851,7 ms. / 0,4 ms.) Depth: 1\n      Usd.Utilities.WebApi.Controllers.UnikOwinContext (0,1 ms. / 0,0 ms.) Depth: 2\n         Usd.Utilities.WebApi.Controllers.UnikOwinContext (0,1 ms. / 0,0 ms.) Depth: 3\n
Run Code Online (Sandbox Code Playgroud)\n\n

一开始我认为代码有问题,并且由于某种原因导致组件多次得到解决。正如 Steven 指出的,当组件注册为InstancePerDependency. 但由于我的几个组件被注册为InstancePerLifetimeSingleInstance项,因此这些依赖项不应在图中解析两次。

\n\n

史蒂文确实提到“依赖关系的第一个解析InstancePerDependency似乎比下一个解析有更多的依赖关系,因为该图仅显示解析。也许这就是正在发生的事情。 ”但是当我看到InstancePerLifetime组件被注册多个时在整个图表中,有好几次,我感觉这里还发生了其他事情。

\n\n

这里可能发生了什么?

\n\n

如何注册依赖项

\n\n

以下代码是我们用来注册程序集的代码:

\n\n
public static void RegisterAssemblies(this ContainerBuilder containerBuilder, IList<Assembly> assemblies, params Type[] typesToExclude)\n{\n  if (containerBuilder != null && assemblies.Any())\n  {\n    var allTypes = assemblies.SelectMany(assembly => assembly.GetTypes()).Where(t => !typesToExclude.Any(t2 => t2.IsAssignableFrom(t))).ToList();\n    RegisterAllClassesWithoutAttribute(containerBuilder, allTypes);\n\n    RegisterClassesThatAreSingleton(containerBuilder, allTypes);\n\n    RegisterClassesThatAreInstancePerLifetimeScope(containerBuilder, allTypes);\n\n    RegisterGenericInterfaces(containerBuilder, allTypes);\n\n    RegisterRealOrTestImplementations(containerBuilder, allTypes);\n\n    RegisterAutofacModules(containerBuilder, allTypes);\n\n    containerBuilder.Register(c => UnikCallContextProvider.CurrentContext).As<IUnikCallContext>();\n  }\n}\n\nprivate static void RegisterAutofacModules(ContainerBuilder containerBuilder, List<Type> allTypes)\n{\n  var modules = allTypes.Where(type => typeof(IModule).IsAssignableFrom(type) && type.GetCustomAttribute<DoNotRegisterInIocAttribute>() == null);\n  foreach (var module in modules)\n  {\n    containerBuilder.RegisterModule((IModule) Activator.CreateInstance(module));\n  }\n}\n\nprivate static void RegisterRealOrTestImplementations(ContainerBuilder containerBuilder, List<Type> allTypes)\n{\n  if (StaticConfigurationHelper.UseRealImplementationsInsteadOfTestImplementations)\n  {\n    var realTypes = allTypes.Where(type => type.GetCustomAttribute<RealImplementationAsInstancePerLifetimeScopeAttribute>() != null).ToArray();\n    containerBuilder.RegisterTypes(realTypes).AsImplementedInterfaces()\n      .InstancePerLifetimeScope();\n  }\n  else\n  {\n    var testTypes = allTypes.Where(type => type.GetCustomAttribute<TestImplementationAsInstancePerLifetimeScopeAttribute>() != null).ToArray();\n    containerBuilder.RegisterTypes(testTypes).AsImplementedInterfaces()\n      .InstancePerLifetimeScope();\n  }\n}\n\nprivate static void RegisterGenericInterfaces(ContainerBuilder containerBuilder, List<Type> allTypes)\n{\n  var typesAsGenericInterface = allTypes.Where(type => type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>() != null).ToArray();\n  foreach (var type in typesAsGenericInterface)\n  {\n    var attribute = type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>();\n    containerBuilder.RegisterGeneric(type).As(attribute.Type);\n  }\n}\n\nprivate static void RegisterClassesThatAreInstancePerLifetimeScope(ContainerBuilder containerBuilder, List<Type> allTypes)\n{\n  var typesAsInstancePerDependency = allTypes.Where(type => type.GetCustomAttribute<InstancePerLifetimeScopeAttribute>() != null).ToArray();\n  containerBuilder.RegisterTypes(typesAsInstancePerDependency).InstancePerLifetimeScope().AsImplementedInterfaces();\n}\n\nprivate static void RegisterClassesThatAreSingleton(ContainerBuilder containerBuilder, List<Type> allTypes)\n{\n  var typesAsSingleton = allTypes.Where(type => type.GetCustomAttribute<SingletonAttribute>() != null).ToArray();\n  containerBuilder.RegisterTypes(typesAsSingleton).SingleInstance().AsImplementedInterfaces();\n}\n\nprivate static void RegisterAllClassesWithoutAttribute(ContainerBuilder containerBuilder, List<Type> allTypes)\n{\n  var types = allTypes.Where(type => !typeof(IModule).IsAssignableFrom(type) &&\n                                     type.GetCustomAttribute<DoNotRegisterInIocAttribute>() == null &&\n                                     type.GetCustomAttribute<SingletonAttribute>() == null &&\n                                     type.GetCustomAttribute<RealImplementationAsInstancePerLifetimeScopeAttribute>() == null &&\n                                     type.GetCustomAttribute<TestImplementationAsInstancePerLifetimeScopeAttribute>() == null &&\n                                     type.GetCustomAttribute<InstancePerLifetimeScopeAttribute>() == null &&\n                                     type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>() == null).ToArray();\n  containerBuilder.RegisterTypes(types).AsSelf().AsImplementedInterfaces();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

RegisterAssemblies可以像这样获取传递给该方法的程序集:

\n\n
private List<Assembly> GetAssemblies()\n{\n  var assemblies = AssemblyResolveHelper.LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory,\n    new Regex(@"Usd.EA.*\\.dll"),\n    SearchOption.TopDirectoryOnly);\n  assemblies.AddRange(AssemblyResolveHelper.LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory,\n    new Regex(@"Usd.Utilities.*\\.dll"),\n    SearchOption.TopDirectoryOnly));\n\n  assemblies.Add(GetType().Assembly);\n  return assemblies.Distinct().ToList();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

属性

\n\n

中使用的属性RegisterAllClassesWithoutAttribute是我们手动分配给各个类的自定义属性

\n\n
using System;\n\n[AttributeUsage(AttributeTargets.Class)]\npublic class DoNotRegisterInIocAttribute : Attribute\n{\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

像这样使用

\n\n
[ExcludeFromCodeCoverage]\n[DoNotRegisterInIoc]\npublic sealed class TestClass : ITestClass\n
Run Code Online (Sandbox Code Playgroud)\n\n

当我不覆盖 Autofacs 时,MaxResolveDepth出现以下错误

\n\n
\n

失败 尝试创建 \n \'BogfoerController\' 类型的控制器时发生错误。确保控制器具有无参数\n 公共构造函数。激活时抛出异常 \xce\xbb:Usd.EA\n .Bogfoering.WebApi.Controllers.BogfoerController ->\n Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController -> ......\n 可能工厂范围内的组件之间的循环依赖。链\n 包括\'Activator = DomainWrapper (DelegateActivator)、Services =\n SomeService、Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime、\n 共享= None、所有权=ExternallyOwned\'

\n
\n

Mat*_*lak 5

简短回答: 这是由 Autofac 行为在解析ILifetimeScope通过调用 创建的子项的服务时引起的BeginLifetimeScope(Action<ContainerBuilder> configurationAction)

长答案: 我已经设置了一个简单的测试来证明上述说法。我已经生成了 51 个引用自身的测试类。

public class Test0
{
    public Test0() { }
}

public class Test1
{
    public Test1(Test0 test) { }
}

(...)

public class Test50
{
    public Test50(Test49 test) { }
}
Run Code Online (Sandbox Code Playgroud)

将它们注册到新创建的容器中,并尝试直接从容器解析“Test50”类。正如你已经发现的那样。Autofac 库中存在 50 个依赖项深度的硬编码限制,您可以在GitHub页面上看到它。达到此限制后,DependencyResolutionException会抛出“工厂范围的组件之间可能存在循环依赖”的错误消息。这正是我的第一次测试中发生的情况。

现在您可能会问,为什么会看到相同依赖项的多个注册。那么有趣的部分来了。当您尝试解析实例时,您可能会使用该BeginLifetimeScope函数来创建新的 ILifetimeScope。这仍然没问题,除非您要使用其中一个重载向子作用域添加一些新的注册。请参阅下面的示例:

using (var scope = container.BeginLifetimeScope(b => { }))
{
    var test = scope.Resolve<Test49>();
}
Run Code Online (Sandbox Code Playgroud)

我只解决了 50 个依赖项(以前有效),但现在它产生了一个异常:

异常信息

正如您所看到的,这与您之前描述的行为完全相同。每个依赖项现在显示 2 次。在该图像上,您还可以看到依赖关系图仅到达该类Test25。这有效地将之前的最大深度减少了一半(总共 25 个依赖项!)。我们可以通过成功解析Test24类来测试这一点,但是在尝试解析Test25. 这更有趣,你认为,如果我们添加另一个作用域会发生什么?

using (var scope1 = container.BeginLifetimeScope(b => { }))
{
    using (var scope2 = scope1.BeginLifetimeScope(b => { }))
    {
        var test2 = scope2.Resolve<Test49>();
    }
}
Run Code Online (Sandbox Code Playgroud)

您可能已经猜到了,现在您只能解析深度 50 / 3 = ~16 的依赖关系。

还有一个例外

结论:创建嵌套作用域限制了依赖关系图的实际可用最大深度 N 倍,其中 N 是作用域的深度。老实说,在不扩展容器构建器的情况下创建的作用域不会影响这个数字。在我看来,这是一个巨大的荒谬,拥有硬编码的幻数,它在文档中没有任何地方,无法轻松配置,甚至不代表实际的最大深度,并且当溢出时,它会抛出误导性的异常,表明您图中某处存在循环依赖关系。

解决方案:作为此问题的解决方案,您不能使用此函数的此重载。由于架构限制,甚至可能使用 Autofac 作为 DI 容器的第 3 方框架,这可能是不可能的。

您已经提到的另一个解决方案是使用脏反射覆盖 MaxResolveDepth。

string circularDependencyDetectorTypeName = typeof(IContainer).AssemblyQualifiedName.Replace(typeof(IContainer).FullName, "Autofac.Core.Resolving.CircularDependencyDetector");
Type circularDependencyDetectorType = Type.GetType(circularDependencyDetectorTypeName);
FieldInfo maxResolveDepthField = circularDependencyDetectorType.GetField("MaxResolveDepth", BindingFlags.Static | BindingFlags.NonPublic);

maxResolveDepthField.SetValue(null, 500);
Run Code Online (Sandbox Code Playgroud)

在 Autofac 的 GitHub 上,您还可以看到他们已经计划更改 的行为CircularDependencyDetector,以便它可以处理无限深度的依赖关系,但这些计划是在 2018 年提到的,他们甚至无法在这个日期之前更改该异常消息。