AOP interceptors

IT IT
Добавил базовый перехватчик и несколько типовых аспектов на его базе: LoggingAspect, CacheAspect и CounterAspect. Хотел ещё сделать PermissionAspect, но там получается всё достаточно интимно и как сделать общее решение не понятно.

Использование.

LogginAspect:

public abstract class TestClass
{
    // Лог конкретного метода.
    //
    [Log]
    public virtual void Test(int p1)
    {
    }

    // Логировать вызовы, выполняющиеся более 0.5 сек.
    //
    [Log("MinCallTime=500")]
    public virtual void Test(int p1)
    {
    }
}

// Лог всех методов класса.
//
[Log]
public abstract class TestClass
{
    public virtual void Test1(int p1)
    {
    }

    // Отключить лог для данного метода
    //
    [NoLog]
    public virtual void Test2(int p1)
    {
    }
}

void Test()
{
    // Параметры, влияющие на все вызовы, исключая те, где эти параметры заданы явно.
    //
    LoggingAspect.LogParameters = true;      // логировать значения параметров вызываемого метода. Умолчание - true.
    LoggingAspect.LogExceptions = true;      // логировать исключения. Умолчание - true.
    LoggingAspect.MinCallTime   = 1000;      // логировать вызовы дольше 1 секунды. Умолчание - 0.
    LoggingAspect.FileName      = "log.txt"; // выводить в файл. Умолчание - null - Debug.WriteLine.
    LoggingAspect.IsEnabled     = false;     // оключить лог. Умолчание - true.

    TestClass t = TypeAccessor.CreateInstance<TestClass>();

    t.Test(1);
}

CacheAspect кеширует результат вызова методов в зависимости от значения параметров. Кешируется возвращаемое значние и ref/out параметры.

public abstract class TestClass
{
    // Кешировать результат вызова метода.
    //
    [Cache]
    public virtual ArrayList Test(int p1)
    {
        return new ArrayList(p1);
    }
}

// Кешировать результаты вызова всех методов класса на 1 секунду.
//
[Cache(1000)]
public abstract class TestClass
{
    [NoCache]
    public virtual void Test1(int p1)
    {
    }
}

void Test()
{
    // Параметры, влияющие на все вызовы, исключая те, где эти параметры заданы явно.
    //
    CacheAspect.MaxCacheTime = 1000;  // длительность кеширования. Умолчание - int.MaxValue.
    CacheAspect.IsWeak       = true;  // использовать слабые ссылки. Умолчание - true.
    CacheAspect.IsEnabled    = false; // оключить кеширование. Умолчание - true.

    TestClass t = TypeAccessor.CreateInstance<TestClass>();

    t.Test(1);
}

CounterAspect собирает статистику по вызовам методов.

public abstract class TestClass
{
    // Статистика вызовов метода.
    //
    [Counter]
    public virtual ArrayList Test(int p1)
    {
        return new ArrayList(p1);
    }
}

// Собирать статистику вызовов всех методов класса.
//
[Counter]
public abstract class TestClass
{
    [NoCounter]
    public virtual void Test1(int p1)
    {
    }
}

void Test()
{
    // Параметры, влияющие на все вызовы, исключая те, где эти параметры заданы явно.
    //
    CounterAspect.IsEnabled = false; // оключить сбор статистики. Умолчание - true.

    TestClass t = TypeAccessor.CreateInstance<TestClass>();

    t.Test(1);

    lock (CounterAspect.Counters.SyncRoot)
    {
        foreach (CounterAspect.Counter c in CounterAspect.Counters)
        {
            c.MethodInfo   // метаданные метода
            c.TotalTime    // общее время выполнения всех завершённых вызовов
            c.TotalCount   // общее количество завершённых вызовов
            c.CurrentCalls // текущие вызовы

            // Здесь интерес могут представлять следующие поля
            InterceptCallInfo ci = (InterceptCallInfo)c.CurrentCalls[0];

            (IPrincipal)ci.Items["CurrentPrincipal"]; // кто вызвал метод
            ci.BeginCallTime                          // когда
        }
    }
}
... << RSDN@Home 1.2.0 alpha rev. 0>>
Блудов Павел
Блудов Павел
13.06.2006 03:56
Здравствуйте, IT!

Хм... Вкусно! Как новый "орбит InterceptorAttribute".

Попробовал на живом проекте — сразу захотелось вынести настройку в конфиг.
А фиг вам! TypeExtension тут либо не работает, либо я не умею её готовить.

Вообще, вынос атрибутов во внешний файл идея сама по себе очень грамонтная. Например, можно вынести все запросы во внешний файл:

        [TypeExtension(FileName="PersonAccessor.xml")]
        public abstract class PersonAccessor : DataAccessor
        {
            [SqlQuery("SELECT * FROM Person")]
            public abstract Person SelectAll();


В данном примере можно задать [SqlQuery("SELECT * FROM Person")] во внешней xml-ке. Это очень круто, так как все запросы можно собрать в одном месте и иметь две версии — одну для production, одну для future release. Или одну для MSSQL а другую для Oracle. Жаль, что я не умею их готовить.
Скупая и устарелая информация не даёт возможности оценить размах и пустить в дело.
... << RSDN@Home 1.2.0 alpha rev. 642>>
IT
IT
13.06.2006 04:19
Здравствуйте, Блудов Павел, Вы писали:

БП>Попробовал на живом проекте — сразу захотелось вынести настройку в конфиг.

БП>А фиг вам! TypeExtension тут либо не работает, либо я не умею её готовить.

Не работает. Надо делать что-то типа метадата провайдера. Но пока что-то в кучу не складывается.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Блудов Павел
Блудов Павел
13.06.2006 09:59
Здравствуйте, IT, Вы писали:

IT>Надо делать что-то типа метадата провайдера.

Надо. В принципе, если не замахиваться на незамахиваемое, можно просто прикрутить всюду TypeExtension вместо Type.GetAttributes()
Только прийдётся добавить немного кода чтобы свести всё к простым типам.
В случае с InterceptorAttribute, например, можно вместо
    public class InterceptorAttribute
    {
        private readonly Type _interceptorType;
        public           Type  InterceptorType
        {
            get { return _interceptorType; }
        }
    }

заделать
    public class InterceptorAttribute
    {
        private readonly string _interceptorTypeName;     // Это в Xml задать не сложно
        private readonly Type _interceptorType;         // А это удобно задавать в аттрибуте
        public           Type  InterceptorType
        {
            get
            {
                return (null != _interceptorType) ? _interceptorType : _interceptorType = Type.GetType(_interceptorTypeName);
            }
        }
    }


Даже если всё задать через Xml не получится, всё равно овчинка стоит того, чтобы её уделали. Два вполне жизненных примера я привёл выше по теме.
... << RSDN@Home 1.2.0 alpha rev. 642>>
Andy77
Andy77
01.09.2006 07:22
Здравствуйте, IT, Вы писали:

IT>Добавил базовый перехватчик и несколько типовых аспектов на его базе: LoggingAspect, CacheAspect и CounterAspect. Хотел ещё сделать PermissionAspect, но там получается всё достаточно интимно и как сделать общее решение не понятно.


Классно!

IT>CacheAspect кеширует результат вызова методов в зависимости от значения параметров. Кешируется возвращаемое значние и ref/out параметры.


Было бы неплохо добавить возможность кешировать результаты вызовов методов не только в зависимости от параметров, но и в зависимости от экземпляра объекта. Кстати, как вообще можно получить экземпляр объекта в Interceptor'e?
Andy77
Andy77
01.09.2006 10:51
Здравствуйте, IT, Вы писали:

IT>LogginAspect:


А зачем LoggingAspect переписывает файл при каждом логе, почему бы просто не добавлять в конец файла?

private static void LogOutputInternal(string logText, string fileName)
{
    if (fileName == null || fileName.Length == 0)
        Debug.WriteLine(logText);
    else
        using (StreamWriter sw = new StreamWriter(fileName)) 
            sw.WriteLine(logText);
}
PeterZT
PeterZT
18.09.2006 08:52
Здравствуйте, IT

Как из перехватчика можно доступиться до самого объекта, метод которого перехвачен?

Спасибо
... << RSDN@Home 1.1.4 beta 6a rev. 436>>
IT
IT
18.09.2006 12:14
Здравствуйте, PeterZT, Вы писали:

PZT>Как из перехватчика можно доступиться до самого объекта, метод которого перехвачен?


Забавно, но до сих пор ссылка на сам объект никому не понадобилась

Добавил свойство Object в InterceptCallInfo.
Andy77
Andy77
18.09.2006 04:04
Здравствуйте, IT, Вы писали:

IT>Забавно, но до сих пор ссылка на сам объект никому не понадобилась


Двумя неделями раньше и двумя сообщениями выше — http://rsdn.ru/Forum/?mid=2089427&amp;flat=0

Скоро, чувствую, ты напишешь, что забавно, что никому до сих пор не понадобился лог-файл
IT
IT
18.09.2006 04:59
Здравствуйте, Andy77, Вы писали:

IT>>Забавно, но до сих пор ссылка на сам объект никому не понадобилась

A>Двумя неделями раньше и двумя сообщениями выше — http://rsdn.ru/Forum/?mid=2089427&amp;flat=0

Пропустил

A>Скоро, чувствую, ты напишешь, что забавно, что никому до сих пор не понадобился лог-файл


А разве этого нет?

[Log(FileName="...")]


Либо глобально:

LoggingAspect.FileName = "...";
Andy77
Andy77
18.09.2006 05:34
Здравствуйте, IT, Вы писали:

A>>Скоро, чувствую, ты напишешь, что забавно, что никому до сих пор не понадобился лог-файл


IT>А разве этого нет?


В другом сообщении, тоже двумя неделями раньше и тремя сообщениями выше — http://rsdn.ru/Forum/?mid=2089602&amp;flat=0
IT
IT
18.09.2006 11:55
Здравствуйте, Andy77, Вы писали:

IT>>А разве этого нет?


A>В другом сообщении, тоже двумя неделями раньше и тремя сообщениями выше — http://rsdn.ru/Forum/?mid=2089602&amp;flat=0


И это пропустил
... << RSDN@Home 1.2.0 alpha rev. 0>>
adontz
adontz
21.09.2006 12:17
Здравствуйте, IT, Вы писали:

Это очень хорошо, но немного плохо

Как приделать внешний логгер?
IT
IT
21.09.2006 12:29
Здравствуйте, adontz, Вы писали:

A>Как приделать внешний логгер?


LoggingAspect.LogOperation = MyLogOperation;

private static void MyLogOperation(InterceptCallInfo info)
{
  ....
}

Реализацию MyLogOperation можно подсмотреть в LoggingAspect.cs.
adontz
adontz
21.09.2006 01:20
Здравствуйте, IT, Вы писали:

A>>Как приделать внешний логгер?


IT>
IT>LoggingAspect.LogOperation = MyLogOperation;

IT>private static void MyLogOperation(InterceptCallInfo info)
IT>{
IT>  ....
IT>}
IT>

IT>Реализацию MyLogOperation можно подсмотреть в LoggingAspect.cs.

Гуд. Будем пользовать.