6

我有一个实现两个接口的类,我想对类的方法应用拦截。

我遵循Unity Register two interfaces as one singleton中的建议,但我对结果感到惊讶。简而言之,似乎我的 CallHandler 被调用了两次。我有的最短的例子是这样的:

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”行会使日志只出现一次。添加第三个接口 I3,与 I2 类似,会导致日志出现 3 次。

我本来希望日志只被调用一次。我可以通过让 LogCallHandler 检测它是否从另一个 LogCallHandler 调用来实现这一点,但这似乎不优雅。

最初我想将拦截行为应用于 C 而不是分别应用于 I1 和 I2,但这需要 C 从 MarshalByRefObject 继承,这是我还不愿意强加的约束。

有替代方法吗?

4

2 回答 2

5

问题是您将透明代理应用于每个接口。相反,如果将其应用于具体类,您只会得到一个代理。此外,除非您希望共享实例,否则您不需要将其设为单例。

我在一个测试控制台项目中运行了这个配置并得到了想要的结果。感谢您包含一个可以隔离您的问题的工作片段!

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();
}

唯一的区别是我在RegisterType的调用之外设置了I1和I2的拦截。上面的输出是:

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

这给了我以下信息:

  • 我截取了 C 对 I1 和 I2 的实现。
  • 只有一个 C 实例(由对 Data 成员的修改证明)。
  • 我可以到达 C 的单个实例(即container.Resolve<C>()有效,尽管没有拦截)。

我的用例是单元测试:我的代码依赖于拦截来实现其某些功能(特别是事务管理)。C 是我提供给测试类的一个模拟对象,它实现了测试类所需的两个接口。当单元测试运行时,我想访问模拟对象来验证它上面的东西。

上述解决方案的缺点是我只能在这里应用PolicyInjectionBehavior(实际上,上面的代码没有指定任何InterceptionBehavior:使用时container.Configure<Interception>,Unity会自动使用PolicyInjectionBehavior,据我所知。对于我的用例, 它是可以接受的。

我不得不承认,我很惊讶在 RegisterType 期间设置拦截会产生与单独配置不同的结果。如果有人可以解释这一点,我很乐意理解为什么会这样。

于 2011-03-21T06:14:09.510 回答