4

nServiceBus を基になる IoC コンテナーとして Ninject 2.0 と連携させようとしましたが、うまくいきませんでした。基本的な統合はできますが、「ゴースト」メッセージがさまざまなサブスクライバーに送信されるという問題がありました。Autofac の実装を一種のテンプレートとして使用し、必要な部分を Ninject 固有のコードに置き換えました。さらに、自動プロパティ インジェクションを発生させるためにカスタム ヒューリスティックを作成する必要がありました。

いずれにせよ、私が目にする動作は、最初のメッセージが発行され、サブスクライバーによって正常に読み取られるということです。ただし、パブリッシュされる次のメッセージでは、メッセージが 3 回「受信」されます。

だから、私は疑問に思っています:nServiceBus ObjectBuilderとしてNinjectで何かをしている人はいますか? または、現在 nServiceBus 2.0 にバンドルされている他の IoC コンテナー (つまり、Windsor、StructureMap、または Autofac) の統合中に、この動作を確認して修正した人はいますか。

編集: 私はこれを見ましが、完全には見えず、プロパティ注入のヒューリスティックは少し違うはずだと思いました。

4

5 に答える 5

5

nservicebus グループでこれについて議論しているスレッドがありますが、まだ解決策はありません。

http://tech.groups.yahoo.com/group/nservicebus/message/6253

于 2010-03-09T12:51:33.807 に答える
4

私は2つの問題を抱えていましたが、解決策を見つけました。

最初の問題は、オブジェクトが私のクラスのIContainer.ConfigureメソッドでNinject カーネルに登録/構成された方法に起因していました。NinjectObjectBuilder他の IoC コンテナーを使用してnServiceBus の既存の実装を調べた結果、ObjectBuilder登録の一般的な方法は、具体的な型自体と、その型が実装するすべてのインターフェイスを登録することであることに気付きました。Ninject では、これは「具象型をそれ自体にバインド」し、その型が実装する各インターフェイスもその型にバインドすることになります。dotTraceでプロファイリングした後に見つけたものを除いて、これはかなり簡単でした。シングルトン アクティベーションの場合、実際にシングルトン参照を取得しているようには見えませんでした。実際には、要求されたサービスのタイプに応じて、新しいオブジェクトが取得されます。たとえば、UnicastBus具象型はシングルトン スコープに実装され、登録されますIBusIStartableBusnServiceBus は、IBusorIStartableBusが要求されたかどうかにかかわらず、それらがシングルトンであり、両方が同じ実装に「バインド」されている場合、同じオブジェクトを受け取ることを期待しています。Ninject のシングルトンの解釈は、サービスまたはインターフェイスに関するものであるように見えます。つまり、UnicastBusを要求するたびに、同じインスタンスを取得しますIBus。ただし、新しい、別UnicastBusの要求を受け取りますIStartableBus. IContainer.Configureこれを解決する方法は、次のようにメソッドを実装することでした。

void IContainer.Configure(Type concreteComponent, 
                                 ComponentCallModelEnum callModel) {

  if (HasComponent(concreteComponent))
    return;

  var services = concreteComponent.GetAllServices()
    .Where(t => t != concreteComponent);

  var instanceScope = GetInstanceScopeFrom(callModel);
  // Bind the concrete type to itself ...
  kernel
    .Bind(concreteComponent)
    .ToSelf()
    .InScope(instanceScope);

  // Bind "aliases" to the binding for the concrete component
  foreach (var service in services)
    kernel
      .Bind(service)
      .ToMethod(ctx => ctx.Kernel.Get(concreteComponent))
      .InScope(instanceScope);
}

これにより、nServiceBus の期待と一致する方法でシングルトン インスタンスを解決するという問題が解決されました。ただし、ハンドラーで「ゴースト」メッセージを受信するという問題がまだありました。ここここ で説明されているように、log4net ログ ファイルをくまなく調べ、プロファイリングし、最後に問題を読み取った後。この問題は、プロパティ インジェクション中に複数のイベント ハンドラーがアタッチされていることが原因で発生します。具体的には、この問題は、UnicastBus のTransportプロパティが複数回設定されているために発生します。nServiceBus コード ベースの UnicastBus.cs のコード スニペットを次に示します。

public virtual ITransport Transport
{
  set
  {
    transport = value;

    transport.StartedMessageProcessing += TransportStartedMessageProcessing;
    transport.TransportMessageReceived += TransportMessageReceived;
    transport.FinishedMessageProcessing += TransportFinishedMessageProcessing;
    transport.FailedMessageProcessing += TransportFailedMessageProcessing;
  }

}

考えてみると、なぜこのプロパティが複数回設定されているのか疑問に思いました。 UnicastBusは nServiceBus によってシングルトン スコープに登録されており、上記で説明したようにその問題を修正したところです。結局のところ、Ninject は、オブジェクトをアクティブ化するときに (新規であれ内部キャッシュからであれ)、依然としてオブジェクトのプロパティを注入しようとします。内部カーネル コンポーネント コンテナーに登録されたインジェクション ヒューリスティック クラスを呼び出し、それらの応答 (つまり、その呼び出しの結果) に基づいて、bool ShouldInject(MemberInfo member)実装、) 各アクティベーションの前にプロパティを挿入します。そのため、解決策は、Ninject が以前にアクティブ化されたシングルトンのインスタンスでプロパティ インジェクションを実行しないようにすることでした。私の解決策は、スコープが一時的ではない以前にアクティブ化されたインスタンスを追跡する新しいプロパティ注入戦略を作成し、そのようなインスタンスのアクティブ化リクエストに対してデフォルトのプロパティ注入戦略をスキップすることでした。私の戦略は次のようになります。

/// <summary>
/// Only injects properties on an instance if that instance has not 
/// been previously activated.  This forces property injection to occur 
/// only once for instances within a scope -- e.g. singleton or within 
/// the same request, etc.  Instances are removed on deactivation.
/// </summary>
public class NewActivationPropertyInjectStrategy : PropertyInjectionStrategy {
  private readonly HashSet<object> activatedInstances = new HashSet<object>();

  public NewActivationPropertyInjectStrategy(IInjectorFactory injectorFactory)
    : base(injectorFactory) { }

  /// <summary>
  /// Injects values into the properties as described by 
  /// <see cref="T:Ninject.Planning.Directives.PropertyInjectionDirective"/>s
  /// contained in the plan.
  /// </summary>
  /// <param name="context">The context.</param>
  /// <param name="reference">A reference to the instance being 
  /// activated.</param>
  public override void Activate(IContext context, 
                                         InstanceReference reference) {

    if (activatedInstances.Contains(reference.Instance)) 
      return;    // "Skip" standard activation as it was already done!

    // Keep track of non-transient activations...  
    // Note: Maybe this should be 
    //       ScopeCallback == StandardScopeCallbacks.Singleton
    if (context.Binding.ScopeCallback != StandardScopeCallbacks.Transient)
      activatedInstances.Add(reference.Instance);

    base.Activate(context, reference);
  }

  /// <summary>
  /// Contributes to the deactivation of the instance in the specified context.
  /// </summary>
  /// <param name="context">The context.</param>
  /// <param name="reference">A reference to the instance being 
  /// deactivated.</param>
  public override void Deactivate(IContext context, 
                                  InstanceReference reference) {

    activatedInstances.Remove(reference.Instance);
    base.Deactivate(context, reference);
  }
}

私の実装は現在機能しています。私が抱えていた他の唯一の課題は、プロパティ注入の既存のアクティベーション戦略を「置き換える」ことでした。カスタム カーネルを作成することを考えました (これが最善の方法かもしれません)。ただし、nServiceBus 用の「事前構成された」カーネルをサポートすることはできません。今のところ、Ninject カーネルに新しいコンポーネントを追加する拡張メソッドがあります。

public static void ConfigureForObjectBuilder(this IKernel kernel) {
  // Add auto inject heuristic
  kernel.Components.Add<IInjectionHeuristic, AutoInjectBoundPropertyTypeHeuristic>();

  // Replace property injection activation strategy...
  /* NOTE: I don't like this!  Thinking about replacing the pipeline component
   * in Ninject so that it's smarter and selects our new activation 
   * property inject strategy for components in the NServiceBus DLLs and 
   * uses the "regular strategy" for everything else.  Also, thinking of 
   * creating a custom kernel.
   */
  kernel.Components.RemoveAll<IActivationStrategy>();
  kernel.Components.Add<IActivationStrategy, 
                            NewActivationPropertyInjectStrategy>();
  // The rest of the "regular" Ninject strategies ...
  kernel.Components.Add<IActivationStrategy, MethodInjectionStrategy>();
  kernel.Components.Add<IActivationStrategy, InitializableStrategy>();
  kernel.Components.Add<IActivationStrategy, StartableStrategy>();
  kernel.Components.Add<IActivationStrategy, BindingActionStrategy>();
  kernel.Components.Add<IActivationStrategy, DisposableStrategy>();
}

これは、既存のコンポーネントを「置き換える」ためのメカニズムがカーネル コンポーネント コンテナーにないため、現時点では完全なハッキングです。また、プロパティ インジェクション戦略の既存の動作をオーバーライドしたかったため、これらの特定のタイプの戦略を一度に複数カーネルに含めることはできませんでした。この現在の実装のもう 1 つの問題は、IActivationStrategy構成されている可能性のある他のカスタム コンポーネントが失われることです。IActivationStrategyリスト内のすべてのコンポーネントを取得し、それらをカーネルから削除し、作成したリスト内のプロパティ インジェクション戦略を置き換えてから、それらすべてをカーネルに戻して効果的に置き換えるコードを書きたかったのです。ただし、カーネル コンポーネント コンテナーはジェネリックのみをサポートします。Addメソッドと動的呼び出しを作成するためのファンキーなコードを書く気がしませんでした。

**編集** 昨日投稿した後、戦略をより適切に処理することにしました。Ninjectカーネルを構成するために拡張メソッドにすべてをバンドルして、私がしたことは次のとおりです。

public static void ConfigureForObjectBuilder(this IKernel kernel) {
  // Add auto inject heuristic
  kernel.Components.Add<IInjectionHeuristic, AutoInjectBoundPropertyTypeHeuristic>();

  // Get a list of all existing activation strategy types
  // with exception of PropertyInjectionStrategy
  var strategies = kernel.Components.GetAll<IActivationStrategy>()
    .Where(s => s.GetType() != typeof (PropertyInjectionStrategy))
    .Select(s => s.GetType())
    .ToList();
  // Add the new property injection strategy to list
  strategies.Add(typeof (NewActivationPropertyInjectStrategy));

  // Remove all activation strategies from the kernel
  kernel.Components.RemoveAll<IActivationStrategy>();

  // Add the list of strategies 
  var addMethod = kernel.Components.GetType().GetMethod("Add");
  strategies
    .ForEach(
    t => addMethod
           .MakeGenericMethod(typeof (IActivationStrategy), t)
           .Invoke(kernel.Components, null)
    );
}
于 2010-03-09T15:21:40.757 に答える
3

私のアイデアを公式リポジトリに取り込みました:

https://github.com/NServiceBus/NServiceBus/tree/master/src/impl/ObjectBuilder/ObjectBuilder.Ninject

ダニエル

于 2010-03-10T09:58:16.187 に答える
1

ハイピーター、

戦略を動的に交換するアプローチを見つけました(これはNinjectObjectBuilderのコンストラクターで行います)。

            this.kernel.Bind<NewActivationPropertyInjectStrategy>().ToSelf().WithPropertyValue("Settings", ctx => ctx.Kernel.Settings);
        this.kernel.Bind<IInjectorFactory>().ToMethod(ctx => ctx.Kernel.Components.Get<IInjectorFactory>());

        this.kernel.Components.Get<ISelector>().InjectionHeuristics.Add(this.propertyHeuristic);

        IList<IActivationStrategy> activationStrategies = this.kernel.Components.Get<IPipeline>().Strategies;

        IList<IActivationStrategy> copiedStrategies = new List<IActivationStrategy>(
            activationStrategies.Where(strategy => !strategy.GetType().Equals(typeof(PropertyInjectionStrategy)))
            .Union(new List<IActivationStrategy> { this.kernel.Get<NewActivationPropertyInjectStrategy>() }));

        activationStrategies.Clear();
        copiedStrategies.ToList().ForEach(activationStrategies.Add);
于 2010-03-10T08:18:23.120 に答える
0

NinjectObjectBuilder の使用方法の例を次に示します。

この例では、Web サイトとバックエンドの両方に Ninject 依存性注入を使用して、NServiceBus を介してバックエンドにコマンドを送信する Web サイトを示しています。

Daniel Marbach によってリンクされている公式リポジトリから NinjectObjectBuilder をコピーしました。私の知る限り、コードはまだ NServiceBus のメインストリーム バージョンの一部としてリリースされていません。今日から使用したいので、コードをコピーして NServiceBus 2.6 にリンクしました。

于 2012-01-13T22:16:47.820 に答える