【问题标题】:Autofac Singleton OnActivating ResolveAutofac Singleton OnActivating Resolve
【发布时间】:2015-06-18 18:13:23
【问题描述】:

我误解的症结在于,我希望直接 Resolve() 嵌套方法中的一个类型,该类型是由于 OnActivating 事件而调用的,对于相同的单例类型,并且 autofac 正在尝试创建第二个实例那个单身人士。

更长的版本:

先来个完整的例子,再总结一下:

public static class AutofacTest
{
    public static void Test()
    {
        var builder = new ContainerBuilder();

        // Register the environment as a singleton, and call Initialize when created
        builder.RegisterType<Environment>().AsSelf().As<IEnvironment>().SingleInstance().OnActivating(e => e.Instance.Initialize());

        // Register the simulator, also a singleton and dependent on 
        builder.RegisterType<Simulator>().AsSelf().As<ISimulator>().SingleInstance();

        // Register a simple class, that needs an initialized environment
        builder.RegisterType<IndependentClass>();

        // Build/scope
        var context = builder.Build();
        var scope = context.BeginLifetimeScope();

        // Register the service locator
        ServiceLocator.GlobalScope = scope;

        //var childScope = scope.BeginLifetimeScope(cb =>
        //{
        //    cb.RegisterType<IndependentClass>();
        //});

        // Now resolve the independent class, which will trigger the environment/simulator instantiation
        var inst = scope.Resolve<IndependentClass>();
    }
}

public static class ServiceLocator
{
    public static ILifetimeScope GlobalScope { get; set; }
}

public interface IEnvironment 
{
    bool IsInitialized { get; }
}

public class Environment : IEnvironment
{
    private static Environment Instance;

    private SampleComponent _component;
    private bool _isInitialized;

    public bool IsInitialized
    {
        get { return _isInitialized; }
    }

    public void Initialize()
    {
        if (Instance != null) throw new InvalidOperationException();
        Instance = this;

        // Canonical complex code which forces me into what I think is a tricky situation...

        _component = new SampleComponent(SampleServiceType.SimulatedThing);

        _component.Initialize();

        _isInitialized = true;
    }
}

public interface ISimulator { }

public class Simulator : ISimulator
{
    private static Simulator Instance;

    private readonly IEnvironment _environment;

    public Simulator(IEnvironment environment)
    {
        if (Instance != null) throw new InvalidOperationException();
        Instance = this;

        _environment = environment;
    }
}

public enum SampleServiceType
{
    None = 0,
    RealThing,
    SimulatedThing,
}

public class SampleComponent
{
    private readonly SampleServiceType _serviceType;

    public SampleComponent(SampleServiceType serviceType)
    {
        _serviceType = serviceType;
    }

    public void Initialize()
    {
        // Sample component that has different types of repositories
        switch (_serviceType)
        {
            case SampleServiceType.SimulatedThing:
                var sim = ServiceLocator.GlobalScope.Resolve<ISimulator>();
                // Create a repositiry object or something requriing the simulator
                break;
        }
    }
}

public class IndependentClass
{
    public IndependentClass(IEnvironment env)
    {
        if (!env.IsInitialized) throw new InvalidOperationException();
    }
}

那么关键点:

  • Environment 是顶级容器,模拟器依赖于环境,环境的组件 (SampleComponent) 依赖于环境和模拟器。

  • 关键的是,组件并不总是使用模拟器(并不罕见),所以这里有一个工厂样式模式的地方。在这种情况下,我通常会使用全局服务定位器(我相信我理解为什么这可能是邪恶的,而且很可能会在这里咬我) - 但主要原因是正是为了类似模拟器(或经常用于UI 目的),我不想在构造函数中依赖模拟器,因为它只在某些场景中使用。 (更多下文。)

  • 环境应在创建后初始化。因此在这里使用OnActivating,除了一个警告外,它运作良好......

  • IndependentClass 采用IEnvironment,此时我想要一个完全初始化的IEnvironment。但在这种情况下,IndependentClass 的解析是触发IEnvironment 解析的原因。所以如果我使用OnActivated,那么我们没有解决问题,但是直到调用构造函数之后环境才被初始化。

实际问题(终于!):

如前所述,目前发生的情况:

  • Resolve&lt;IndependentClass&gt; 触发..
  • Resolve&lt;IEnvironment&gt;
  • OnActivating&lt;Environment&gt; 触发 Environment.Initialize...
  • 然后调用SampleComponent.Initialize...
  • 调用全局作用域Resolve&lt;IEnvironment&gt;..
  • 然后解析/实例化第二个Environment

因此,即使我已将Environment 注册为单例,也会创建两个实例。

这不是一个错误,它似乎是预期的行为(因为 Initialize 调用发生在 OnActivating 并且该实例尚未注册),但我应该怎么做才能解决这个问题?

我想要求:

  • SampleComponentResolve 时,环境Resolve 会延迟发生。 (因为并不总是需要环境。)

  • Environment.Initialize 调用是在实例传递给 SampleComponent ctor 之前进行的。

  • SampleComponent 不必采用 ISimulator ctor 参数,因为它通常不是必需的。 (但我不会反对将工厂模式重组为更适合 Autofac 的东西,只要我不必要求我的(非顶级)组件支持 Autofac。)

基本上,我只想要求在使用 IEnvironment 实例之前进行初始化调用,并且由于 Environment/SampleComponent/Simulator 对象图是完全独立的,这似乎应该能够接线/表达。

我尝试过的事情:

  • 首先明确解决贸易环境:如前所述,这是可行的,但我觉得这个要求有点太受限制了。主要是因为我喜欢在构建容器之后(但在解析环境之前)允许一些可选配置(​​通过 UI 或其他方式),并且由于并不总是需要环境(或模拟器),所以我不需要不想实例化它,直到需要它。 (IStartableAutoActivate 也是如此,除非我没有看到其他使用它们的方法。)

  • 放弃服务定位器模式。不过,在这种情况下,我需要表示 SampleComponent 需要为 serviceType 的某些值解析 ISimulator,否则将 null 传递给构造函数(或 Property/etc)。有没有简洁的表达方式?

  • 最后,创建我自己的实例注册,并将 Environment 实例存储为静态单例。比如:

    builder.Register(c => CreateInstance()).AsSelf().As().SingleInstance().OnActivating(e => e.Instance.Initialize());

    地点:

        private static Environment _globalInstance;
    private static Environment CreateInstance()
    {
        if (_globalInstance == null)
        {
            _globalInstance = new Environment();
        }
        return _globalInstance;
    }
    

    这可行,但是: 1. OnActivating 仍然为每个“新”实例调用。 2. 感觉太老套了——最终我现在正在管理实例和构造,这就是容器的用途。 (当你真的想使用容器来解析参数时,这也有点烦人,但同样可以很容易地解决。)

说了这么多(我非常感谢你能做到这一点),看来我在这里有一个根本的误解。 (我猜它与SampleComponent 中的服务定位器模式和/或随意工厂有关,但我将停止推测​​。)

我想真正的问题是:我错过了什么?

【问题讨论】:

  • 看起来你有一个被服务位置掩盖的循环引用。 Environment => SampleComponent => Simulator => Environment。如果你在Environment 中加了一个锁(如果你不使用静态构造函数,我建议单例初始化)你会死锁。你应该放松一下,看看你是否还有问题。
  • @Travis - 同意存在循环依赖,虽然作为最佳实践当然不鼓励这样做,但它是 supported。也就是说,构造/注入中的依赖关系 - Initialize 调用应该在注入后发生并且不应该有任何问题,除了在触发 OnActivating 之前没有注册实例的事实之外。 (同意锁,实际上我总是会使用它。但它不会死锁,因为我相信不会在锁内调用 Initialize。)
  • 我知道它是受支持的(我编写了文档),但它在以构造函数或注入属性表示的依赖关系的上下文中受支持 - 而不是您在运行服务定位器时破坏链的方式在中间。我很快就会发布一个更大的答案。
  • “我写了文档”——这很难反驳!好的,我肯定很期待,在此期间会考虑一下。感谢您的洞察力先生!

标签: c# design-patterns inversion-of-control autofac


【解决方案1】:

尝试从示例中运行您的确切代码,我无法解析IndependentClass,因为我(正确地)得到了一个异常。异常堆栈看起来像一个循环依赖,它嵌套和嵌套并嵌套相同的异常,就像堆栈溢出:

Autofac.Core.DependencyResolutionException was unhandled
  _HResult=-2146233088
  _message=An exception was thrown while executing a resolve operation. See the InnerException for details.
  HResult=-2146233088
  IsTransient=false
  Message=An exception was thrown while executing a resolve operation. See the InnerException for details. ---> Operation is not valid due to the current state of the object. (See inner exception for details.)
  Source=Autofac
  StackTrace:
       at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
       at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context)
       at SingletonRepro.SampleComponent.Initialize() in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 120
       at SingletonRepro.Environment.Initialize() in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 75
       at SingletonRepro.Program.<Main>b__0(IActivatingEventArgs`1 e) in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 17
       at Autofac.Builder.RegistrationBuilder`3.<>c__DisplayClass6.<OnActivating>b__5(Object s, ActivatingEventArgs`1 e)
       at Autofac.Core.Registration.ComponentRegistration.RaiseActivating(IComponentContext context, IEnumerable`1 parameters, Object& instance)
       at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
       at Autofac.Core.Resolving.InstanceLookup.<Execute>b__0()
       at Autofac.Core.Lifetime.LifetimeScope.GetOrCreateAndShare(Guid id, Func`1 creator)
       at Autofac.Core.Resolving.InstanceLookup.Execute()
       at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Resolving.InstanceLookup.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Activators.Reflection.AutowiringParameter.<>c__DisplayClass2.<CanSupplyValue>b__0()
       at Autofac.Core.Activators.Reflection.ConstructorParameterBinding.Instantiate()
       at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
       at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
       at Autofac.Core.Resolving.InstanceLookup.Execute()
       at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Resolving.ResolveOperation.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
       at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context)
       at SingletonRepro.Program.Main(String[] args) in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 38
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: System.InvalidOperationException
       _HResult=-2146233079
       _message=Operation is not valid due to the current state of the object.
       HResult=-2146233079
       IsTransient=false
       Message=Operation is not valid due to the current state of the object.
       Source=SingletonRepro
       StackTrace:
            at SingletonRepro.Environment.Initialize() in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 68
            at SingletonRepro.Program.<Main>b__0(IActivatingEventArgs`1 e) in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 17
            at Autofac.Builder.RegistrationBuilder`3.<>c__DisplayClass6.<OnActivating>b__5(Object s, ActivatingEventArgs`1 e)
            at Autofac.Core.Registration.ComponentRegistration.RaiseActivating(IComponentContext context, IEnumerable`1 parameters, Object& instance)
            at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
            at Autofac.Core.Resolving.InstanceLookup.<Execute>b__0()
            at Autofac.Core.Lifetime.LifetimeScope.GetOrCreateAndShare(Guid id, Func`1 creator)
            at Autofac.Core.Resolving.InstanceLookup.Execute()
            at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters)
            at Autofac.Core.Resolving.InstanceLookup.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
            at Autofac.Core.Activators.Reflection.AutowiringParameter.<>c__DisplayClass2.<CanSupplyValue>b__0()
            at Autofac.Core.Activators.Reflection.ConstructorParameterBinding.Instantiate()
            at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
            at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
            at Autofac.Core.Resolving.InstanceLookup.<Execute>b__0()
            at Autofac.Core.Lifetime.LifetimeScope.GetOrCreateAndShare(Guid id, Func`1 creator)
            at Autofac.Core.Resolving.InstanceLookup.Execute()
            at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters)
            at Autofac.Core.Resolving.ResolveOperation.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
            at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters)
       InnerException: ...

在对问题的评论中,您正确指出 Autofac does support circular dependencies。确实如此,但在单个解决周期的上下文中确实如此。这里的问题是通过在中间添加服务位置,特别是在SampleComponent.Initialize方法中,已经打破了单一的解析链。

无论你如何堆叠它——问题是你以某种方式得到了两个单例还是你得到了这个异常——归根结底是需要打破这种循环依赖。

如果您绝对必须使用服务位置,打破依赖关系的一种方法是使用 the Lazy&lt;T&gt; relationship 它的作用是为您提供延迟解决方案零件。在您的 SampleComponent.Initialize 方法中,将服务定位方法更改为如下所示:

var sim = ServiceLocator.GlobalScope.Resolve<Lazy<ISimulator>>();

如果您创建需要ISimulator 的存储库,请尝试更改该存储库的构造函数以采用Lazy&lt;ISimulator&gt;,并且仅在最后可能的时刻调用Lazy&lt;ISimulator&gt;.Value。这将延迟Environment 的解析操作足够长的时间,以让整个链在第一次正确完成并让您摆脱循环解析问题。

更好的选择是重构以一直使用 DI。 现在您正在通过代码混合依赖注入、服务定位和手动实例构造。 Environment 手动创建一个SampleComponentSampleComponent 使用服务位置获取ISimulatorISimulator 使用 DI 获得 IEnvironment。像这样的混搭会让你陷入你现在看到的各种麻烦。

事实上,一直使用 DI 意味着您实际上不需要在任何地方实现单例模式 - 只需使用构造函数并根据需要注册 SingleInstance

这是您的代码的更新版本(以控制台应用程序形式),显示了一些可能会做什么的想法。显然您的真实代码可能更复杂,因此我无法逐字向您展示每种边缘情况的所有可能解决方案,但这是打破链条的一种方法。您可以利用此处和其他 available implicit relationship types 的想法来找出解决挑战的方法。

using System;
using Autofac;
using Autofac.Features.Indexed;

namespace SingletonRepro
{
  class Program
  {
    static void Main()
    {
      var builder = new ContainerBuilder();

      // You can still keep the Initialize call if you want.
      builder.RegisterType<Environment>().As<IEnvironment>().SingleInstance().OnActivated(args => args.Instance.Initialize());

      // Everything's in DI now, not just some things.
      builder.RegisterType<Simulator>().As<ISimulator>().SingleInstance();
      builder.RegisterType<IndependentClass>();

      // Using keyed services to choose the repository rather than newing things up.
      builder.RegisterType<RealRepository>().Keyed<IRepository>(SampleServiceType.RealThing);
      builder.RegisterType<SimulatedRepository>().Keyed<IRepository>(SampleServiceType.SimulatedThing);
      builder.RegisterType<SampleComponent>().WithParameter("serviceType", SampleServiceType.SimulatedThing);

      var context = builder.Build();
      using (var scope = context.BeginLifetimeScope())
      {
        // Using Lazy<T> in the IndependentClass to defer the need for
        // IEnvironment right away - breaks the dependency circle.
        var inst = scope.Resolve<IndependentClass>();
        inst.DoWork();
        Console.WriteLine("Instance: {0}", inst);
      }
    }
  }

  public interface IEnvironment
  {
    bool IsInitialized { get; }
  }

  public class Environment : IEnvironment
  {
    public SampleComponent _component;

    public Environment(SampleComponent component)
    {
      this._component = component;
    }

    public void Initialize()
    {
      this._component.DoSomethingWithRepo();
      this.IsInitialized = true;
    }

    public bool IsInitialized { get; private set; }
  }

  public interface ISimulator
  {
  }

  public class Simulator : ISimulator
  {
    public Simulator(IEnvironment environment)
    {
      this.Environment = environment;
    }
    public IEnvironment Environment { get; private set; }
  }

  public enum SampleServiceType
  {
    None = 0,
    RealThing,
    SimulatedThing,
  }

  public class SampleComponent
  {
    private IIndex<SampleServiceType, IRepository> _repositories;

    private SampleServiceType _serviceType;

    // Use indexed/keyed services to pick the right one from a dictionary
    // rather than newing up the repository (or whatever) manually.
    public SampleComponent(IIndex<SampleServiceType, IRepository> repositories, SampleServiceType serviceType)
    {
      this._repositories = repositories;
      this._serviceType = serviceType;
    }

    public void DoSomethingWithRepo()
    {
      // You could always take the service type parameter in this function
      // rather than as a constructor param.
      var repo = this._repositories[this._serviceType];
      repo.DoWork();
    }
  }

  public interface IRepository
  {
    void DoWork();
  }

  public class SimulatedRepository : IRepository
  {
    private ISimulator _simulator;

    public SimulatedRepository(ISimulator simulator)
    {
      this._simulator = simulator;
    }

    public void DoWork()
    {
    }
  }

  public class RealRepository : IRepository
  {
    public void DoWork()
    {
    }
  }

  public class IndependentClass
  {
    private Lazy<IEnvironment> _env;

    // Delaying the need for the IEnvironment in the constructor
    // can help break the circular dependency chain, as well as not
    // immediately checking that it's initialized. (Can you just
    // TRUST that it's initialized and call it good?)
    public IndependentClass(Lazy<IEnvironment> env)
    {
      this._env = env;
    }

    public void DoWork()
    {
      if (!this._env.Value.IsInitialized)
        throw new InvalidOperationException();
    }
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多