Autofac 多次注册组件

Autofac registers components multiple times

我得到了代码的基础,我现在用它来可视化我的依赖图,因为它是由 Autofac 解析的。

运行 代码我得到了一棵树,结果代码如下。

Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController (3851,7 ms. / 0,0 ms.) Depth: 0
   Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController (3851,7 ms. / 0,4 ms.) Depth: 1
      Usd.Utilities.WebApi.Controllers.UnikOwinContext (0,1 ms. / 0,0 ms.) Depth: 2
         Usd.Utilities.WebApi.Controllers.UnikOwinContext (0,1 ms. / 0,0 ms.) Depth: 3

一开始我以为代码有问题,并且由于某种原因导致组件被多次解析。正如 Steven 指出的那样,当组件注册为 InstancePerDependency 时可能会发生这种情况。但是由于我的几个组件被注册为 InstancePerLifetimeSingleInstance 依赖项,这些依赖项不应在图中解析两次。

InstancePerDependency 依赖项的第一个解析似乎比下一个解析具有更多的依赖项,因为此图仅显示解析。也许这就是发生的事情on." 但是当我看到 InstancePerLifetime 组件被多次注册时,在整个图表中有几次,我觉得这里还有其他事情发生。

这里可能发生了什么?

如何注册依赖项

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

public static void RegisterAssemblies(this ContainerBuilder containerBuilder, IList<Assembly> assemblies, params Type[] typesToExclude)
{
  if (containerBuilder != null && assemblies.Any())
  {
    var allTypes = assemblies.SelectMany(assembly => assembly.GetTypes()).Where(t => !typesToExclude.Any(t2 => t2.IsAssignableFrom(t))).ToList();
    RegisterAllClassesWithoutAttribute(containerBuilder, allTypes);

    RegisterClassesThatAreSingleton(containerBuilder, allTypes);

    RegisterClassesThatAreInstancePerLifetimeScope(containerBuilder, allTypes);

    RegisterGenericInterfaces(containerBuilder, allTypes);

    RegisterRealOrTestImplementations(containerBuilder, allTypes);

    RegisterAutofacModules(containerBuilder, allTypes);

    containerBuilder.Register(c => UnikCallContextProvider.CurrentContext).As<IUnikCallContext>();
  }
}

private static void RegisterAutofacModules(ContainerBuilder containerBuilder, List<Type> allTypes)
{
  var modules = allTypes.Where(type => typeof(IModule).IsAssignableFrom(type) && type.GetCustomAttribute<DoNotRegisterInIocAttribute>() == null);
  foreach (var module in modules)
  {
    containerBuilder.RegisterModule((IModule) Activator.CreateInstance(module));
  }
}

private static void RegisterRealOrTestImplementations(ContainerBuilder containerBuilder, List<Type> allTypes)
{
  if (StaticConfigurationHelper.UseRealImplementationsInsteadOfTestImplementations)
  {
    var realTypes = allTypes.Where(type => type.GetCustomAttribute<RealImplementationAsInstancePerLifetimeScopeAttribute>() != null).ToArray();
    containerBuilder.RegisterTypes(realTypes).AsImplementedInterfaces()
      .InstancePerLifetimeScope();
  }
  else
  {
    var testTypes = allTypes.Where(type => type.GetCustomAttribute<TestImplementationAsInstancePerLifetimeScopeAttribute>() != null).ToArray();
    containerBuilder.RegisterTypes(testTypes).AsImplementedInterfaces()
      .InstancePerLifetimeScope();
  }
}

private static void RegisterGenericInterfaces(ContainerBuilder containerBuilder, List<Type> allTypes)
{
  var typesAsGenericInterface = allTypes.Where(type => type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>() != null).ToArray();
  foreach (var type in typesAsGenericInterface)
  {
    var attribute = type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>();
    containerBuilder.RegisterGeneric(type).As(attribute.Type);
  }
}

private static void RegisterClassesThatAreInstancePerLifetimeScope(ContainerBuilder containerBuilder, List<Type> allTypes)
{
  var typesAsInstancePerDependency = allTypes.Where(type => type.GetCustomAttribute<InstancePerLifetimeScopeAttribute>() != null).ToArray();
  containerBuilder.RegisterTypes(typesAsInstancePerDependency).InstancePerLifetimeScope().AsImplementedInterfaces();
}

private static void RegisterClassesThatAreSingleton(ContainerBuilder containerBuilder, List<Type> allTypes)
{
  var typesAsSingleton = allTypes.Where(type => type.GetCustomAttribute<SingletonAttribute>() != null).ToArray();
  containerBuilder.RegisterTypes(typesAsSingleton).SingleInstance().AsImplementedInterfaces();
}

private static void RegisterAllClassesWithoutAttribute(ContainerBuilder containerBuilder, List<Type> allTypes)
{
  var types = allTypes.Where(type => !typeof(IModule).IsAssignableFrom(type) &&
                                     type.GetCustomAttribute<DoNotRegisterInIocAttribute>() == null &&
                                     type.GetCustomAttribute<SingletonAttribute>() == null &&
                                     type.GetCustomAttribute<RealImplementationAsInstancePerLifetimeScopeAttribute>() == null &&
                                     type.GetCustomAttribute<TestImplementationAsInstancePerLifetimeScopeAttribute>() == null &&
                                     type.GetCustomAttribute<InstancePerLifetimeScopeAttribute>() == null &&
                                     type.GetCustomAttribute<RegisterAsGenericInterfaceAttribute>() == null).ToArray();
  containerBuilder.RegisterTypes(types).AsSelf().AsImplementedInterfaces();
}

交付给 RegisterAssemblies 方法的程序集可以像这样获取:

private List<Assembly> GetAssemblies()
{
  var assemblies = AssemblyResolveHelper.LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory,
    new Regex(@"Usd.EA.*\.dll"),
    SearchOption.TopDirectoryOnly);
  assemblies.AddRange(AssemblyResolveHelper.LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory,
    new Regex(@"Usd.Utilities.*\.dll"),
    SearchOption.TopDirectoryOnly));

  assemblies.Add(GetType().Assembly);
  return assemblies.Distinct().ToList();
}

属性

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

using System;

[AttributeUsage(AttributeTargets.Class)]
public class DoNotRegisterInIocAttribute : Attribute
{
}

像这样使用

[ExcludeFromCodeCoverage]
[DoNotRegisterInIoc]
public sealed class TestClass : ITestClass

当我不覆盖 Autofacs 时 MaxResolveDepth 我收到以下错误

Failed An error occurred when trying to create a controller of type 'BogfoerController'. Make sure that the controller has a parameterless public constructor. An exception was thrown while activating λ:Usd.EA .Bogfoering.WebApi.Controllers.BogfoerController -> Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController -> ...... Probable circular dependency between factory-scoped components. Chain includes 'Activator = DomainWrapper (DelegateActivator), Services = SomeService, Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = None, Ownership = ExternallyOwned'

简答: 这是由 Autofac 在解析通过调用 BeginLifetimeScope(Action<ContainerBuilder> configurationAction).

创建的子 ILifetimeScope 的服务时的行为引起的

长答案: 我已经建立了一个简单的测试来证明上面的说法。我生成了 51 个测试 classes 引用它们自己。

public class Test0
{
    public Test0() { }
}

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

(...)

public class Test50
{
    public Test50(Test49 test) { }
}

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

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

using (var scope = container.BeginLifetimeScope(b => { }))
{
    var test = scope.Resolve<Test49>();
}

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

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

using (var scope1 = container.BeginLifetimeScope(b => { }))
{
    using (var scope2 = scope1.BeginLifetimeScope(b => { }))
    {
        var test2 = scope2.Resolve<Test49>();
    }
}

你应该猜对了,现在只能解决depth 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);

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