【问题标题】:Prevent Ninject from calling Initialize multiple times when binding to several interfaces绑定多个接口时防止Ninject多次调用Initialize
【发布时间】:2010-06-15 08:03:48
【问题描述】:

我们有一个具体的单例服务,它实现了Ninject.IInitializable 和 2 个接口。问题是服务 Initialize-methdod 被调用了 2 次,而只需要一次。我们正在使用 .NET 3.5 和 Ninject 2.0.0.0。

Ninject 中是否有防止这种情况发生的模式。这两个接口都没有实现Ninject.IInitializable。服务类是:

public class ConcreteService : IService1, IService2, Ninject.IInitializable
{
    public void Initialize()
    {
        // This is called twice!
    }
}

模块看起来像这样:

public class ServiceModule : NinjectModule
{
    public override void Load()
    {
        this.Singleton<Iservice1, Iservice2, ConcreteService>();
    }
}

其中 Singleton 是这样定义的扩展方法:

    public static void Singleton<K, T>(this NinjectModule module) where T : K
    {
        module.Bind<K>().To<T>().InSingletonScope();
    }

    public static void Singleton<K, L, T>(this NinjectModule module) 
        where T : K, L
    {
        Singleton<K, T>(module);
        module.Bind<L>().ToMethod(n => n.Kernel.Get<T>());
    }

当然,我们可以将 bool initialized-member 添加到 ConcreteService 并仅在它为 false 时才进行初始化,但这似乎有点 hack。而且它需要在实现两个或多个接口的每个服务中重复相同的逻辑。


感谢所有答案!我从他们所有人身上学到了一些东西! (我很难决定哪一个标记是正确的)。

我们最终创建了 IActivable 接口并扩展了 ninject 内核(它还很好地删除了对 ninject 的代码级依赖,尽管属性仍然存在)。

【问题讨论】:

标签: ninject


【解决方案1】:

忍者 3

Ninject 3.0 现在在绑定调用中支持多种泛型类型,您尝试做的事情可以在单个链式语句中轻松完成。

kernel.Bind<IService1, IService2>()
      .To<ConcreteService>()
      .InSingletonScope();

忍者2

您正在设置两个不同的绑定 K=>T 和 L=>T。请求 L 的实例将返回 T 的瞬时实例。请求 K 将返回 T 的单例实例。

在 Ninject 2.0 中,对象作用域是每个服务接口绑定到作用域回调的。

当你有

Bind<IFoo>...InSingletonScope();
Bind<IBar>...InSingletonScope();

您正在创建两个不同的范围。

你是说 “绑定到 IFoo 将解析为返回的相同对象 当 .Get 被调用时。 和 “绑定到 IBar 将解析为返回的相同对象 何时调用 .Get。”

您可以将绑定链接在一起,但您需要删除 IInitializable,因为它会在激活实例时导致重复初始化:

kernel.Bind<IBoo>()
      .To<Foo>()
      .InSingletonScope();
      .OnActivation(instance=>instance.Initialize());

kernel.Bind<IBaz>()
      .ToMethod( ctx => (IBaz) ctx.Kernel.Get<IBoo>() );

kernel.Bind<Foo>().ToSelf().InSingletonScope()
    .OnActivation(instance=>instance.Initialize());
kernel.Bind<IBaz>().ToMethod( ctx => ctx.Kernel.Get<Foo>() );
kernel.Bind<IBoo>().ToMethod( ctx => ctx.Kernel.Get<Foo>() );

为了让多个接口解析到同一个单例实例。当我看到这样的情况时,我总是要问,如果你有一个有两个职责的单例,你的对象是不是做得太多了?

【讨论】:

  • 你确定?在我的答案中添加了一个编辑 - AIUI 每次激活 Bind 都会导致初始化(如果我没有遗漏什么,我会非常惊讶!)
  • 已更新。我忘记了 IIinitializable 部分。
  • 现在同意该建议并同意它更好地解决了 OP 的问题。不确定我是否同意您将两个 Bind...InSingletonScope 绘制为创建不同的范围上下文的方式 - 范围最终是同一个根。
  • 现在没有 PC,但需要仔细检查 ToMethod 中的每个 Kernel.Get&lt;Foo&gt;() 调用是否不是单独的“激活” - 即,我认为它仍然可能被多次调用(如果确实如此,则流利的语法顺序混乱)。我真的希望有人能解决这个问题的道场更新——我可以对小问题进行逆向工程,但不能理解这个领域的禅意(双关语)。
  • 请注意,现在 V3 中有一个合适的解决方案(除了 Then Dont Do That):- planetgeek.ch/2011/12/30/…
【解决方案2】:

更新:很确定使用V3's multiple Bind overloads 会解决这个问题;见this Q/A


好问题。

从源代码来看,初始化位发生在每个Activate 之后。你的Bind...ToMethod 也算一个。该策略的应用非常一致 - 在特定情况下无法选择退出。

您的解决方法选项是在您的Bind 中使用显式的OnActivation,这将有条件地执行此操作(但以一般方式执行此操作需要维护一组初始化对象(尚未查看是否有机制来针对激活的对象存储标志)),或者通过任何对您来说最干净的方式使您的 Initialize 幂等。

编辑:

    internal interface IService1
    {
    }

    internal interface IService2
    {
    }

    public class ConcreteService : IService1, IService2, Ninject.IInitializable
    {
        public int CallCount { get; private set; }
        public void Initialize()
        {
            ++CallCount;
        }
    }

    public class ServiceModule : NinjectModule
    {
        public override void Load()
        {
            this.Singleton<IService1, IService2, ConcreteService>();
        }
    }

给定以下助手:

static class Helpers
{
    public static void Singleton<K, T>( this NinjectModule module ) where T : K
    {
        module.Bind<K>().To<T>().InSingletonScope();
    }

    public static void Singleton<K, L, T>( this NinjectModule module )
        where T : K, L
    {
        Singleton<T, T>( module );
        module.Bind<K>().ToMethod( n => n.Kernel.Get<T>() );
        module.Bind<L>().ToMethod( n => n.Kernel.Get<T>() );
    }
}

@Ian Davis 等人。问题是:

    class Problem
    {
        [Fact]
        static void x()
        {
            var kernel = new StandardKernel( new ServiceModule() );
            var v1 = kernel.Get<IService1>();
            var v2 = kernel.Get<IService2>();
            var service = kernel.Get<ConcreteService>();
            Console.WriteLine( service.CallCount ); // 3
            Assert.AreEqual( 1, service.CallCount ); // FAILS
        }
    }

因为每个激活(每个Bind)每次都会初始化。

编辑 2:当您使用以下稍微精简的版本时也是如此:

static class Helpers
{
    public static void Singleton<K, L, T>( this NinjectModule module )
        where T : K, L
    {
        module.Bind<T>().ToSelf().InSingletonScope();
        module.Bind<K>().ToMethod( n => n.Kernel.Get<T>() );
        module.Bind<L>().ToMethod( n => n.Kernel.Get<T>() );
    }
}

【讨论】:

    【解决方案3】:

    我认为其中一种选择是,您在模块中创建您自己的对象并将您的对象绑定到每个接口。

    顺便说一句,尽量不要在生产代码中使用任何特定于容器的代码。如果必须这样做,请使用一些帮助程序并将它们隔离在模块项目中。

    public class ServiceModule : NinjectModule
    {
    
        public override void Load()
        { 
             ConcreteService svc = new ConcreteService();
             Bind<IService1>().ToConstant(svc);
             Bind<IService2>().ToConstant(svc);
             ....
         }
    }
    

    【讨论】:

    • 不会工作 - 问题是每次激活都会调用 Ninject 的 IInitialize 接口(即,作为Bind 流的一部分创建)。您的示例归结为与问题相同的内容,因此最终也会初始化两次。
    • 我会这样说,实现 IInitialize 是完全没有必要的,而且是一种不好的做法——如果有一天你必须切换到另一个容器,你的代码需要更改,你必须记住它由容器初始化,如果您忘记了这一点并删除了接口以使其编译,您将遇到运行时问题。另一方面,如果一个“新”可以处理它,为什么要让它这么复杂,反正容器是为我们服务的,让代码更简单,而不是更难,而 Moudle 的目的是构造对象,我不介意“新的”我自己。
    • 我同意你的一般观点(构造函数注入,没有容器细节泄漏到业务逻辑等)。我的关键点和 -1 的原因是 a)您的代码与 OP 说不起作用的问题中的代码相同 b)因此它不能回答或解决问题。 IInitializable 的目的是促进两个阶段的构建,如果您必须使用 prop 注入(例如,如果您的环境排除构造函数注入),它会很有用。但这都是与问题无关的题外话。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 2015-01-07
    • 1970-01-01
    • 2022-10-14
    • 1970-01-01
    相关资源
    最近更新 更多