6

2 つのインターフェイスを実装するクラスがあり、クラスのメソッドにインターセプトを適用したいと考えています。

Unity Register two interfaces as one singletonのアドバイスに従っていますが、結果には驚いています。一言で言えば、私の CallHandler が 2 回呼び出されているようです。私が持っている最短の例はこれです:

public interface I1
{
    void Method1();
}

public interface I2
{
    void Method2();
}

public class C : I1, I2
{
    [Log]
    public void Method1() {}

    public void Method2() {}
}

public class LogAttribute : HandlerAttribute
{
    public override ICallHandler CreateHandler(IUnityContainer container)
    {
        return new LogCallHandler();
    }
}

public class LogCallHandler : ICallHandler
{
    public IMethodReturn Invoke(
        IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        Console.WriteLine("Entering " + input.MethodBase.Name);
        var methodReturn = getNext().Invoke(input, getNext);
        Console.WriteLine("Leaving " + input.MethodBase.Name);
        return methodReturn;
    }

    public int Order { get; set; }
} 

void Test()
{
    IUnityContainer container = new UnityContainer();
    container.AddNewExtension<Interception>();
    container.RegisterType<C>(new ContainerControlledLifetimeManager());

    container.RegisterType<I1, C>(
        new Interceptor<TransparentProxyInterceptor>(),
        new InterceptionBehavior<PolicyInjectionBehavior>());

    container.RegisterType<I2, C>(
        new Interceptor<TransparentProxyInterceptor>(),
        new InterceptionBehavior<PolicyInjectionBehavior>());

    container.Resolve<I1>().Method1();
}

次の出力が得られます。

Entering Method1
Entering Method1
Leaving Method1
Leaving Method1

「container.RegisterType I2, C」行を削除すると、ログが 1 回だけ表示されます。I2 に似た 3 番目のインターフェイス I3 を追加すると、ログが 3 回表示されます。

Log が 1 回だけ呼び出されることを期待していました。LogCallHandler が別の LogCallHandler から呼び出されているかどうかを検出することでおそらくこれを達成できますが、これは洗練されていないようです。

もともと、インターセプト動作を I1 と I2 に個別に適用するのではなく、C に適用したかったのですが、これには C が MarshalByRefObject から継承する必要があり、これはまだ課したくない制約です。

別の方法はありますか?

4

2 に答える 2

5

問題は、各インターフェイスに透過プロキシを適用していることです。代わりに、それを具象クラスに適用すると、プロキシは1つだけになります。また、インスタンスを共有する必要がない限り、シングルトンにする必要はありません。

この構成をテストコンソールプロジェクトで実行したところ、目的の結果が得られました。問題を切り分けた実用的なスニペットを含めてくれたことを称賛します。

var container = new UnityContainer()
    .AddNewExtension<Interception>()
    .RegisterType<I1, C>()
    .RegisterType<I2, C>()
    .RegisterType<C>(
        new ContainerControlledLifetimeManager(),
        new Interceptor<TransparentProxyInterceptor>(),
        new InterceptionBehavior<PolicyInjectionBehavior>()
    );
于 2011-03-20T16:24:40.520 に答える
4

結局のところ、元のスニペットを少し変更すると解決策が得られます。

public interface I1
{
    void Method1();
}

public interface I2
{
    void Method2();
}

public class C : I1, I2
{
    public int Data = 0;

    [Log]
    public void Method1() { Console.WriteLine("Method1 " + Data); Data = 1; }

    [Log]
    public void Method2() { Console.WriteLine("Method2 " + Data); Data = 2; }
}

public class LogAttribute : HandlerAttribute
{
    public override ICallHandler CreateHandler(IUnityContainer container)
    {
        return new LogCallHandler();
    }
}

public class LogCallHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        Console.WriteLine("Entering " + input.MethodBase.Name);
        var methodReturn = getNext().Invoke(input, getNext);
        Console.WriteLine("Leaving " + input.MethodBase.Name);
        return methodReturn;
    }

    public int Order { get; set; }
} 

void Test()
{
    IUnityContainer container = new UnityContainer();
    container.AddNewExtension<Interception>();
    container.RegisterType<C>(new ContainerControlledLifetimeManager());

    container.RegisterType<I1, C>();
    container.RegisterType<I2, C>();
    container.Configure<Interception>().SetInterceptorFor<I1>(new TransparentProxyInterceptor());
    container.Configure<Interception>().SetInterceptorFor<I2>(new TransparentProxyInterceptor());

    container.Resolve<I1>().Method1();
    container.Resolve<I2>().Method2();
    container.Resolve<C>().Method2();
}

唯一の違いは、I1 と I2 のインターセプトを RegisterType の呼び出しの外側に設定したことです。上記の出力は次のとおりです。

Entering Method1
Method1 0
Leaving Method1
Entering Method2
Method2 1
Leaving Method2
Method2 2

これにより、次のことがわかります。

  • I1 と I2 の両方の C の実装をインターセプトします。
  • C のインスタンスは 1 つしかありません (Data メンバーへの変更によって証明されます)。
  • C の 1 つのインスタンスに到達できます (つまりcontainer.Resolve<C>()、インターセプトなしでは動作します)。

ここでの私の使用例は単体テストです。私のコードは、その機能の一部 (具体的にはトランザクション管理) をインターセプトに依存しています。C は、テスト対象のクラスに提供するモック オブジェクトであり、テスト対象のクラスが必要とする 2 つのインターフェイスを実装しています。単体テストが実行されたら、モック オブジェクトにアクセスしてその内容を検証したいと思います。

上記の解決策の欠点は、ここでは PolicyInjectionBehavior しか適用できないことです (実際、上記のコードは InterceptionBehavior を指定していません: を使用するcontainer.Configure<Interception>と、私が知る限り、Unity によって PolicyInjectionBehavior が自動的に使用されます。私のユースケースでは、それは許容されます。

RegisterType 中に傍受を設定すると、個別に設定する場合とは異なる結果が得られることに驚いたことを認めなければなりません。誰かがこれを説明できる場合は、その理由を理解していただければ幸いです。

于 2011-03-21T06:14:09.510 に答える