AOP分离贯穿各领域的关切

时间:2011-04-08 16:23:12

标签: c# castle-dynamicproxy aop cross-cutting-concerns

我正在尝试开始利用面向方面的编程来完成重复性任务。我不确定如何分离问题。我正在使用C#和AOP我正在使用Castle.DynamicProxy(使用Autofac的InterceptedBy功能),但我希望这个问题的答案可以是适用于其他AOP解决方案的一般建议(也许你可以说服我转而使用不同的AOP解决方案。)

例如,我有类似下面的拦截器,它拦截对类的所有方法调用。它目前有两个问题:调用方法时,(1)测量调用的时间,以及(2)在调用方法之前和之后记录方法的名称。

public class TimeLoggingInterceptor : IInterceptor
{
    private ILog m_Log;
    public TimeLoggingInterceptor(ILog log)
    {
        m_Log = log;
    }
    public void Intercept(IInvocation invocation)
    {
        // Logging concerns
        string fullMethodName = invocation.TargetType.Name + "." + invocation.MethodInvocationTarget.Name;
        m_Log.Debug(fullMethodName + " started.");

        // Timing concerns
        DateTime beforeStamp = DateTime.UtcNow;

        // Call method
        invocation.Proceed();

        // Timing concerns
        DateTime afterStamp = DateTime.UtcNow;
        TimeSpan callTime = afterStamp - beforeStamp;

        // Logging concerns
        m_Log.Debug(fullMethodName + " finished. Took " + callTime.TotalMilliseconds + "ms.");
    }
}

我有一种压倒性的感觉,这里的时间问题(测量方法调用花费的时间)应该与日志记录问题(写入日志文件)分开,因为......好吧,它们是单独的问题。我正在考虑做这样的事情,但我不确定如何处理订购或在哪里放置 callTime 变量:

public class TimingInterceptor : IInterceptor
{
    private static ThreadLocal<TimeSpan> callTime = new ThreadLocal<TimeSpan>();
    public static TimeSpan CallTime
    {
        get
        {
            if (!callTime.IsValueCreated) throw new InvalidOperationException("callTime was never set");
            return callTime.Value;
        }
    }

    public void Intercept(IInvocation invocation)
    {
        // Timing concerns
        DateTime beforeStamp = DateTime.UtcNow;

        // Call method
        invocation.Proceed();

        // Timing concerns
        DateTime afterStamp = DateTime.UtcNow;
        callTime.Value = afterStamp - beforeStamp;
    }
}
public class LoggingInterceptor : IInterceptor
{
    private ILog m_Log;
    public LoggingInterceptor(ILog log)
    {
        m_Log = log;
    }

    public void Intercept(IInvocation invocation)
    {
        // Logging concerns
        string fullMethodName = invocation.TargetType.Name + "." + invocation.MethodInvocationTarget.Name;
        m_Log.Debug(fullMethodName + " started.");

        // Call method
        invocation.Proceed();

        // Logging concerns
        m_Log.Debug(fullMethodName + " finished. Took " + TimingInterceptor.CallTime.TotalMilliseconds + "ms.");
    }
}

基本上我认为需要在这里发生的是,不知何故, TimingInterceptor 需要直接拦截方法,然后 LoggingInterceptor 需要包围它。

人们使用什么方法来确保这些问题以正确的顺序发生?我是否链接拦截器,让LoggingInterceptor拦截TimingInterceptor的拦截方法?或者我在拦截器类上放置了某种[InterceptOrder(1|2|3|...)]属性?或者我可以在LoggingInterceptor上添加[InterceptAfter(typeof(TimingInterceptor))]之类的内容吗?

是否有更好的替代方法来使用线程局部变量来跟踪通话时间? 是的,我希望这是线程安全的。我想在堆栈上保留这个变量可能是首选,但我不确定LoggingInterceptor如何在不引入过多耦合的情况下获取TimingInterceptor的句柄(能够在不重新编译LoggingInterceptor的情况下切换TimingInterceptor实现会很好)

2 个答案:

答案 0 :(得分:0)

您是否尝试过将两个拦截器添加到代理并运行代码?我的理解是,如果代理有多个Interceptor,在链中的第一个拦截器中调用Proceed()实际上会调用下一个拦截器,而不是实际执行调用。

答案 1 :(得分:0)

在应用程序中尝试单独解决问题是件好事。但是关注点不能相互依赖。

对于IOC / AOP和全局耦合域中使用的术语可能会感到困惑。 实际上,对AOP范式的关注意味着独立的代码/处理。

在您的情况下,我可以确定“记录关注点/方面”以及时间测量/计算器(开始+停止)与记录器之间的依存关系。

您的AOP框架应只注入依赖于记录器+时间计算器(例如业务/域/功能Stopwath)的Logging Aspect / Advice。理想情况下,记录器和时间计算器位于接口后面,并且记录方面应使用IOC容器通过注入的(请由构造函数提供)记录器和时间计算器实例化记录建议。

通过这种方式,日志建议,日志记录方面,日志记录器模块和时间计算器模块可以由不同团队在不同项目中进行单元测试和开发。

使用threadlocal / threadstatic或callcontext通常是一个坏主意,并且可能反映设计问题。

不是:如果使用threadstatic / threadlocal,请注意内存泄漏/长保留对象,线程池管理,异步调用(任务),由于连贯和随机样式结果而导致难以检测的错误。