#c# #castle-dynamicproxy #aop #cross-cutting-concerns
#c# #castle-dynamicproxy #aop #сквозные проблемы
Вопрос:
Я пытаюсь начать использовать преимущества аспектно-ориентированного программирования для повторяющихся задач. Я не уверен, как разделять проблемы. Я использую 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 для перехвата TimingInterceptor? Или мне поместить какой-то [InterceptOrder(1|2|3|...)]
атрибут в классы перехватчика? Или я могу поместить что-то вроде [InterceptAfter(typeof(TimingInterceptor))]
в LoggingInterceptor?
И есть ли лучшая альтернатива использованию локальной переменной потока для отслеживания времени вызова? Да, я бы хотел, чтобы это было потокобезопасно. Я думаю, что предпочтительнее было бы сохранить эту переменную в стеке, но я не уверен, как LoggingInterceptor мог бы получить дескриптор TimingInterceptor без введения слишком большой связи (было бы неплохо иметь возможность отключить реализацию TimingInterceptor без перекомпиляции LoggingInterceptor).
Комментарии:
1. Мой перехватчик ведения журнала добавляет продолжительность вызова к концу сообщения журнала
2. 1, В AspectJ вы можете указать порядковый номер для метода interceptor; и 2, в вашем случае лично я бы создал единый EnterLeaveInterceptor, который, в свою очередь, вызывает другой MethodService, и организовал функции ведения журнала методов и синхронизации в MethodService. Чтобы их было легко настроить, также избегайте слишком большого количества прокси-классов.
3. Майкл: это именно то, что делает мой первый пример. Я предполагаю, что это отдельная проблема, и я хотел бы максимально разделить ее.
4. Леник: Это кажется вполне приемлемым и дешевым решением (создайте один перехватчик, попросите его вызвать серию методов вокруг точки перехвата). Однако я новичок в AOP и надеюсь начать с хороших практик. Я хотел бы иметь возможность добавлять и удалять перехватчики, не добавляя слишком много обязанностей к одному классу. Если есть простой, масштабируемый способ, которым я могу объединить перехватчики, я могу избежать переноса слишком большого количества обязанностей в один класс и, следовательно, упростить их.
Ответ №1:
Вы пробовали просто добавить оба перехватчика к своему прокси-серверу и запустить свой код? Насколько я понимаю, если прокси-сервер имеет несколько перехватчиков, вызов Procedue() в первом перехватчике в цепочке фактически вызовет следующий перехватчик, а не фактически выполнит вызов.
Комментарии:
1. Это то, чего я ожидаю от него. Но мне нужно учитывать порядок вызовов, поскольку один должен выполняться раньше другого.
2. И, для протокола, да, код работает. Но TimingInterceptor передает информацию о времени обратно в LoggingInterceptor через статическую локальную переменную потока , которая просто ужасно пахнет.
Ответ №2:
Полезно попробовать отдельные проблемы в ваших приложениях. Но проблемы не могут зависеть от других.
Это может привести к путанице в терминологии, используемой в IOC / AOP и глобальной области сопряжения. Действительно, проблемы в парадигме AOP означают независимый код / обработку.
В вашем случае я могу определить «проблему / аспект ведения журнала» и зависимость между измерением времени / калькулятором (старт Стоп) и регистратором.
Для работы в вашей платформе AOP должен быть внедрен только аспект ведения журнала / рекомендации, которые могут зависеть от logger time calculator (например, бизнес / домен / функциональный Stopwath). В идеале, logger и time calculator находятся за интерфейсами, и аспект ведения журнала должен использовать контейнер IOC для создания рекомендаций по ведению журнала с помощью встроенных (пожалуйста, конструктором) logger и time calculator.
Таким образом, рекомендации по ведению журнала, аспект ведения журнала, модуль logger и модуль time calculator могут быть модульным тестированием и разрабатываться разными командами в разных проектах.
Использование threadlocal / threadstatic или callcontext часто является плохой идеей и может отражать проблему дизайна.
нет: если вы используете threadstatic / threadlocal, позаботьтесь об объекте memoryleak / long retention, управлении пулом потоков, асинхронном вызове (задаче), ошибке, которую трудно обнаружить из-за последовательных результатов в случайном стиле.