Autofac Singleton OnActivating Resolve

时间:2015-06-18 18:13:23

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

我误解的关键在于,我希望直接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的Resolve会触发IEnvironment的解析。因此,如果我使用OnActivated,那么我们就没有解决问题,但是在调用构造函数之前,环境尚未初始化。

实际问题(最后!):

如上所述,目前正在发生的事情:

  • Resolve<IndependentClass>触发..
  • Resolve<IEnvironment>
  • OnActivating<Environment>触发Environment.Initialize ...
  • 然后调用SampleComponent.Initialize ...
  • 调用全局范围Resolve<IEnvironment> ..
  • 然后解析/实例化第二个Environment

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

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

我想要求:

  • ResolveSampleComponent时,环境Resolve会在延迟的基础上发生。 (因为不总是需要环境。)

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

  • SampleComponent不必采用ISimulator ctor参数,因为通常不需要它。 (但我不反对将工厂模式重组为更加Autofac友好的东西,只要我不必要求我的(非顶级)组件能够识别Autofac。)

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

我尝试过的事情:

  • 首先明确解决贸易环境:如上所述,这有效,但我觉得这个要求有点过于局限。主要是因为我有一些可选配置,我希望在构建容器之后(通过UI或其他)允许(但在环境解决之前),并且由于环境(或模拟器)并不总是需要,我在需要之前不要实例化它。 (同样适用于IStartableAutoActivate,除非有其他方法可以使用它们,但我没有看到。)

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

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

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

    其中:

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

    但这样做有效:1。OnActivating仍然需要为每个&#34;新&#34;实例。 2.感觉太过于苛刻 - 最终我现在正在管理实例和构造,这就是容器的用途。 (当你真的想要使用容器来解析参数时,它也会有点烦人,但是再次可以很容易地解决这个问题。)

所有这一切(我非常感谢你这么做),似乎我在这里有一个根本的误解。 (我猜它与SampleComponent中的服务定位器模式和/或随意工厂有关,但我不会停止推测。)

我想真正的问题是:我缺少什么?

1 个答案:

答案 0 :(得分:3)

尝试从示例中运行您的确切代码,我无法解析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<T> relationship这样做可以为您提供延迟分辨率对于组件。在SampleComponent.Initialize方法中,将服务定位方法更改为如下所示:

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

如果您创建需要ISimulator的存储库,请尝试更改该存储库的构造函数以获取Lazy<ISimulator>并仅在最后一刻调用Lazy<ISimulator>.Value。这将延迟Environment的分辨率操作,使整个链条第一次正确完成,并让你摆脱圆形分辨率问题。

更好的选择是重构使用DI。现在,您可以通过代码进行混合依赖注入,服务定位和手动实例构建。 Environment手动创建SampleComponent; SampleComponent使用服务地点获取ISimulator; ISimulator使用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();
    }
  }
}