АОП, разделяющий сквозные интересы

Я пытаюсь начать использовать аспектно-ориентированное программирование для повторяющихся задач. Я не уверен, как разделить проблемы. Я использую C#, а для АОП я использую Castle.DynamicProxy (используя функцию Autofac InterceptedBy), но я надеюсь, что ответ на этот вопрос может быть достаточно общим советом, который можно применить и к другим решениям АОП (возможно, вы сможете убедить мне переключиться на другое решение АОП).

Например, у меня есть что-то вроде следующего перехватчика, который перехватывает все вызовы методов класса. В настоящее время у него есть две проблемы: когда вызывается метод, (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, не вводя слишком много связей (было бы неплохо иметь возможность отключить реализацию TimingInterceptor без перекомпиляции LoggingInterceptor) .


person Jay Sullivan    schedule 08.04.2011    source источник
comment
Мой перехватчик ведения журнала добавляет продолжительность вызова в конец сообщения журнала.   -  person Mike Valenty    schedule 08.04.2011
comment
1. В AspectJ вы можете указать порядковый номер метода перехватчика; и 2. В вашем случае лично я бы создал один EnterLeaveInterceptor, который, в свою очередь, вызывает другой MethodService и организует функции регистрации и синхронизации методов в MethodService. Так что их легко настроить, а также избежать слишком большого количества прокси-классов.   -  person Xiè Jìléi    schedule 08.04.2011
comment
Майкл: это именно то, что делает мой первый пример. Я предлагаю, чтобы это была отдельная проблема, и я хотел бы отделить ее как можно больше.   -  person Jay Sullivan    schedule 08.04.2011
comment
Леник: Это кажется вполне приемлемым и дешевым решением (создать один перехватчик, заставить его вызывать серию методов вокруг точки перехвата). Однако я новичок в АОП и надеюсь начать с хороших практик. Я хотел бы иметь возможность добавлять и удалять перехватчики, не добавляя слишком много обязанностей к одному классу. Если есть простой, масштабируемый способ объединения перехватчиков в стек, я могу не помещать слишком много обязанностей в один класс и, следовательно, сделать их простыми.   -  person Jay Sullivan    schedule 08.04.2011


Ответы (2)


Вы пытались просто добавить оба перехватчика в свой прокси и запустить свой код? Насколько я понимаю, если прокси-сервер имеет несколько перехватчиков, вызов Proceed() в первом перехватчике в цепочке фактически вызовет следующий перехватчик, а не фактически выполнит вызов.

person dlev    schedule 08.04.2011
comment
Это то, что я ожидаю от него. Но мне нужно решить порядок вызовов, так как один должен произойти раньше другого. - person Jay Sullivan; 08.04.2011
comment
И, кстати, да, код работает. Но TimingInterceptor передает информацию о времени обратно в LoggingInterceptor через статическую локальную переменную потока, что просто ужасно пахнет. - person Jay Sullivan; 08.04.2011

Хорошо попробовать отдельные проблемы в ваших приложениях. Но заботы не могут зависеть от другого.

Может остаться путаница в терминологии, используемой в IOC/AOP и в глобальной связанной области. Действительно, проблемы в парадигме АОП означают независимый код/обработку.

В вашем случае я могу определить «вопрос/аспект регистрации» и зависимость между измерением/калькулятором времени (Start+Stop) и регистратором.

Ваша структура AOP должна вводить только аспект/рекомендацию по ведению журнала, который может зависеть от регистратора + калькулятор времени (например, бизнес/домен/функциональный Stopwath). В идеале регистратор и вычислитель времени находятся за интерфейсами, а аспект ведения журнала должен использовать контейнер IOC для создания экземпляра рекомендации по ведению журнала с введенным (пожалуйста, конструктором) регистратором и калькулятором времени.

Таким образом, советы по ведению журнала, аспект ведения журнала, модуль регистратора и модуль калькулятора времени могут быть модульно протестированы и разработаны разными командами в разных проектах.

Использование threadlocal/threadstatic или callcontext часто является плохой идеей и может отражать проблему дизайна.

not : если вы используете threadstatic/threadlocal, позаботьтесь об утечке памяти/объекте длительного хранения, управлении пулом потоков, асинхронном вызове (задаче), ошибке, которую трудно обнаружить из-за согласованных и случайных результатов стиля.

person Tony THONG    schedule 07.12.2018