我正在尝试开始利用面向方面的编程来完成重复的任务。我不知道如何分离关注点。我用的是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|...)]
属性放在拦截器类上?或者我可以把[InterceptAfter(typeof(TimingInterceptor))]
放在loginginterceptor上吗?
是否有更好的替代方法来使用线程局部变量来跟踪调用时间?是的,我希望这是线程安全的。我认为在堆栈上保留这个变量可能是首选,但我不确定LoggingInterceptor如何在不引入太多耦合的情况下获得TimingInterceptor的句柄(如果能够在不重新编译LoggingInterceptor的情况下切换出TimingInterceptor实现就太好了)。
您是否尝试过将两个拦截器添加到代理并运行您的代码?我的理解是,如果代理有多个拦截器,在链中的第一个拦截器中调用Proceed()实际上会调用下一个拦截器,而不是实际执行调用。
最好尝试在应用程序中分离关注点。但是关注点不能依赖于另一个关注点。
可能会对IOC/AOP和全局耦合领域中使用的术语产生混淆。实际上,AOP范例中的关注点意味着独立的代码/处理。
在你的情况下,我可以识别"日志关注点/方面"和时间测量/计算器(开始+停止)和记录器之间的依赖关系。
你的AOP框架应该只注入一个Logging Aspect/Advice,它可以依赖于logger +时间计算器(像业务/领域/功能Stopwath)来工作。理想情况下,日志记录器和时间计算器位于接口之后,日志记录方面应该使用IOC容器来实例化日志记录建议,并注入(请通过构造函数)日志记录器和时间计算器。
这样,日志通知、日志方面、日志模块和时间计算器模块可以由不同的团队在不同的项目中进行单元测试和开发。
使用threadlocal/threadstatic或callcontext通常是一个坏主意,可能反映出设计问题。
not:如果你使用threadstatic/threadlocal,注意内存泄漏/长保留对象,线程池管理,异步调用(Task),由于一致性和随机风格结果而难以检测的bug。