[In a previous question about got I visualize the graph of my dependencies我为现在用来可视化依赖关系图的代码奠定了基础,因为它由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
起初我以为代码有问题,由于某种原因,它导致组件被多次解析。正如史蒂文指出的那样,当组件注册为InstancePerDependency
时,可能会发生这种情况。但是由于我的几个组件都注册为InstancePerLifetime
或SingleInstance
依赖项,因此这些依赖项不应在图中两次解析。
Steven does mention表示,“ InstancePerDependency
依赖关系的第一个解析似乎比下一个解析具有更多的依赖,因为此图仅显示了解析。也许这是正在发生的事情。”看到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
MaxResolveDepth
时出现以下错误失败尝试创建类型为控制器时发生错误'BogfoerController'。确保控制器具有无参数公共构造函数。激活λ:Usd.EA时引发异常.Bogfoering.WebApi.Controllers.BogfoerController->Usd.EA.Bogfoering.WebApi.Controllers.BogfoerController-> ......工厂范围内的组件之间可能的循环依赖关系。链包括'Activator = DomainWrapper(DelegateActivator),服务=SomeService,生命周期= Autofac.Core.Lifetime.CurrentScopeLifetime,共享=无,所有权=外部拥有'
简短回答:从调用ILifetimeScope
创建的子BeginLifetimeScope(Action<ContainerBuilder> configurationAction)
解决服务时,Autofac行为会导致这种情况。
长回答:我已经建立了一个简单的测试来证明上述说法。我生成了一个引用自己的51个测试类。
public class Test0
{
public Test0() { }
}
public class Test1
{
public Test1(Test0 test) { }
}
(...)
public class Test50
{
public Test50(Test49 test) { }
}
将它们注册在新创建的容器中,并尝试直接从容器中解析“ Test50”类。正如您已经发现的那样。 Autofac库中有50个依赖项深度的硬编码限制,您可以在GitHub页面上看到它。达到此限制后,将抛出DependencyResolutionException
,并指出“ 工厂范围内的组件之间可能的循环依赖关系。”而这正是我的第一个测试中发生的情况。
现在您已经问过,为什么会看到具有相同依赖项的多个注册。因此,这是有趣的部分。当您尝试解析实例时,可能要使用BeginLifetimeScope
函数来创建新的ILifetimeScope。除非您打算使用重载之一向子作用域添加一些新的注册,否则这仍然可以。请参见下面的示例:
using (var scope = container.BeginLifetimeScope(b => { }))
{
var test = scope.Resolve<Test49>();
}
我仅解决了50个依赖项(以前已经起作用),但是现在,它产生了一个异常:
如您所见,这与您之前描述的行为完全相同。现在每个依赖项显示2次。在该图像上,您还可以看到依赖图仅达到了Test25
类。这有效地将先前的最大深度减少了一半(总共25个依赖项!)。我们可以通过成功解析Test24
类来对此进行测试,但是在尝试解析Test25
时会引发异常。这变得更加有趣,您如何看待,如果我们添加另一个示波器会发生什么?
using (var scope1 = container.BeginLifetimeScope(b => { }))
{
using (var scope2 = scope1.BeginLifetimeScope(b => { }))
{
var test2 = scope2.Resolve<Test49>();
}
}
您可能已经猜到了,现在您只能解析深度50/3 =〜16的依赖项。
结论:创建嵌套作用域限制了依赖关系图的实际可用最大深度N次,其中N是作用域的深度。老实说,在不扩展容器构建器的情况下创建的作用域不会影响此数字。在我看来,这是一个非常荒谬的事情,因为硬编码的幻数在文档中没有出现,无法轻松配置,甚至不能代表实际的最大深度,并且在溢出时会引发误导性异常,说明您在某处的图形中具有循环依赖性。