Autofac多次注册组件 [英] Autofac registers components multiple times

查看:90
本文介绍了Autofac多次注册组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在上一个有关如何可视化依赖关系图的问题中,我为现在使用的代码奠定了基础可视化Autofac解析后的依存关系图.

In a previous question about how I visualize the graph of my dependencies I got the foundation for the code I now use to visualize my dependency graph as it is resolved by Autofac.

运行代码,我得到一棵树,结果如下所示.

Running the code I get a tree that results in code like the following.

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

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

In the start I thought there was a problem with the code, and that it for some reason resulted in the components getting resolved multiple times. As Steven points out, this could happen when a component is registered as InstancePerDependency. But as several of my components are registered as InstancePerLifetime or SingleInstance dependencies, those dependencies shouldn't be resolved twice in the graph.

史蒂文(Steven)确实提到"InstancePerDependency依赖关系的第一个解析似乎比下一个解决方案具有更多的依赖关系,因为此图仅显示了解析.也许是这样.但是当我看到InstancePerLifetime组件被多个注册时几次,在整个图表中的几次,我都感觉这里还有其他事情发生.

Steven does mention that "the first resolve of the InstancePerDependency dependency seems to have more dependencies than the next resolve, because this graph only shows resolves. Perhaps this is what's going on." But as I'm seeing InstancePerLifetime components being registered multiple times, on several occasions throughout the graph, I have the feeling that there's something else going on here.

这可能是怎么回事?

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

The following code is the one we use to register our assemblies:

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方法的程序集:

Where the assemblies that are delivered to the RegisterAssemblies method could be fetched like this:

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中使用的属性是我们手动分配给各个类的自定义属性

The attributes

The attributes used in RegisterAllClassesWithoutAttribute are custom attributes that we manually assign to individual classes

using System;

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

像这样使用

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

当我不覆盖自动传真MaxResolveDepth时,出现以下错误

When I'm not overwriting Autofacs MaxResolveDepth I get the following error

失败尝试创建类型为Controller时发生错误 'BogfoerController'.确保控制器具有无参数 公共构造函数.激活λ:Usd.EA时引发异常 .Bogfoering.WebApi.Controllers.BogfoerController-> Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController-> ...... 工厂范围内的组件之间可能存在循环依赖关系.链 包括'Activator = DomainWrapper(DelegateActivator),服务= SomeService,生命周期= Autofac.Core.Lifetime.CurrentScopeLifetime, 共享=无,所有权=外部所有'

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'

推荐答案

简短答案: 当通过调用BeginLifetimeScope(Action<ContainerBuilder> configurationAction)创建的子ILifetimeScope解析服务时,Autofac行为会导致这种情况.

Short answer: This is casused by the Autofac behaviour when resolving services from a child ILifetimeScope created by calling BeginLifetimeScope(Action<ContainerBuilder> configurationAction).

长答案: 我已经建立了一个简单的测试来证明上述说法.我已经生成了一个引用自己的51个测试类.

Long answer: I have set up a simple test to prove above statement. I have generated a 51 test classes referencing themselves.

public class Test0
{
    public Test0() { }
}

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

(...)

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

将它们注册到新创建的容器中,并尝试直接从容器中解析"Test50"类.正如您已经发现的那样. Autofac库中有50个依赖项深度的硬编码限制,您可以在工厂范围内的组件之间可能存在循环依赖关系.",而这正是我的第一个测试所发生的情况.

Registered them in a newly created container and tried to resolve the "Test50" class directly from the container. As you already found out. There is hard coded limit of 50 dependencies depth in the Autofac library, which you can see it on the GitHub page. After reaching this limit the DependencyResolutionException is thrown stating "Probable circular dependency between factory-scoped components." And this is exactly what happened in my first test.

现在您已经问过,为什么会看到具有相同依赖项的多个注册.因此,这是有趣的部分.当您尝试解析您的实例时,您可能会使用BeginLifetimeScope函数来创建新的ILifetimeScope.除非您打算使用重载之一向子作用域添加一些新的注册,否则这仍然可以.请参见下面的示例:

Now you have asked, why are you seeing multiple registrations of the same dependencies. So here comes the fun part. When you are trying to resolve your instance, you are probably gonna use the BeginLifetimeScope function to create new ILifetimeScope. This would be still ok, unless you are going to add some new registrations to the child scope using one of the overloads. See example below:

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

我只解决了50个依赖关系(以前已经起作用),但是现在,它产生了一个异常:

I'm resolving only 50 dependencies (which have previously worked), but now, it yields an exception:

如您所见,这与您之前描述的行为完全相同.现在每个依赖项显示2次.在该图像上,您还可以看到依赖图仅到达Test25类.这有效地将先前的最大深度减少了一半(总共25个依赖项!).我们可以通过成功解析Test24类来对此进行测试,但是在尝试解析Test25时会引发异常.这甚至更有趣,您如何看待,如果我们添加另一个示波器会发生什么?

As you can see, this is exactly the same behaviour as you previously described. Each dependency is now showed 2 times. On that image, you can also see that the dependency graph has only reached the Test25 class. This has effectively reduced the previous max depth by a half (whole 25 dependencies!). We can test this by successuflly resolving Test24 class, but exception is thrown when trying to resolve the Test25. This goes even funnier, how do you think, what happens if we add another scope?

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

您可能已经猜到了,现在您只能解决深度50/3 =〜16的依赖性.

You probably guessed it, now you can only resolve the dependencies of depth 50 / 3 = ~16.

结论:创建嵌套作用域会限制依赖关系图的实际可用最大深度N次,其中N是作用域的深度.老实说,在不扩展容器构建器的情况下创建的作用域不会影响此数字.在我看来,这是一个非常荒谬的事情,因为硬编码的幻数在文档中没有出现,无法轻松配置,甚至不能代表实际的最大深度,并且在溢出时会引发误导性异常,说明您在某处的图形中具有循环依赖性.

Conclusion: Creating nested scopes is limiting the actual available maximum depth of the dependencies graph N times, where the N is the depth of the scope. To be honest, scopes created without extending the container builder do not affect this number. In my opinion, this is a huge absurd, to have hard-coded magic number, which is nowhere in the documentation, cannot be easily configured, doesn't even represent the actual maximum depth and when overflowed, it throws misleading exception stating that you have circular dependencies in the graph somewhere.

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

Solutions: As a resolution to this issue you could not use this overload of this function. This could be not possible due to architecture limitations, or even the 3rd party framework which could be using the Autofac as DI container.

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

Another solution that you have already mentioned is overwriting the MaxResolveDepth using dirty reflection.

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年被提及,甚至无法更改在该日期之前发出该异常消息.

On the Autofac's GitHub you can also read that they are already planning to change the behaviour of the CircularDependencyDetector, so it could handle the infinite depth of dependencies, but those plans were mentioned in 2018 and they even couldn't change that exception message by this date.

这篇关于Autofac多次注册组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆