1

我正在尝试开始利用面向方面的编程来完成重复性任务。我不确定如何分离关注点。我正在使用 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 的 Intercept 方法?[InterceptOrder(1|2|3|...)]或者我是否在拦截器类上添加了某种属性?或者我可以[InterceptAfter(typeof(TimingInterceptor))]在 LoggingInterceptor 上放置类似的东西吗?

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

4

2 回答 2

0

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

于 2011-04-08T16:36:21.260 回答
0

最好在您的应用程序中尝试单独的关注点。但是关注点不能依赖于另一个关注点。

它可能会混淆 IOC/AOP 和全局耦合域中使用的术语。实际上,AOP 范式中的关注点意味着独立的代码/处理。

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

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

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

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

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

于 2018-12-07T11:49:19.110 回答