-
Notifications
You must be signed in to change notification settings - Fork 0
Аспекты
В общем случае аспект состоит из двух частей: точки прикрепление (join point) и некоторого совета (advice), который должен быть применен в точке прикрепления.
Применимо к библиотеке обе концепции отражаются в двух классах MethodBoundaryAspect
и MethodInterceptAspect
. Классы являются атрибутами, что позволяет отображать через них точки прикрепления и одновременно с этим предоставляют ряд методов для переопределения, в которых непосредственно реализуется логика совета.
- MethodBoundaryAspect. Выполняет переопределенные методы в точках перед и после фактического вызова основного метода. Развернутый вид метода, на который применен аспект данного вида можно представить следующим образом.
interface Foo
{
[MyAspect]
void Bar();
}
// Вызов new FooImpl().Bar(); преобразуется в
try {
MyAspect.OnEntry(pipeline);
Bar();
MyAspect.OnSuccess(pipeline);
}
catch(Exception e) {
MyAspect.OnException(pipeline);
}
finally {
MyAspect.OnExit(pipeline);
}
В случае, когда на метод применено несколько аспектов, в указанных точках срабатывает каждый из них. Сами аспекты упорядочиваются по свойству Order
в порядке убывания. Если порядок не задан, то вызываются сначала аспекты верхнего уровня (примененные на интерфейс), а затем локальные аспекты на методах.
-
MethodInterceptionAspect. Позволяет перехватывать вызов метода объекта. При этом вызов оригинального метода осуществляется посредством вызова метода
Proceed()
входного параметраIInvocation
. Разрешено наличие только одного атрибута данного типа на методе.
- Аспекты можно использовать только на интерфейсы и методы интерфейсов, на классах, свойствах, конструкторах и прочем - работать не будет;
- Дублирующиеся аспекты удаляются. То есть, если на интерфейсе есть аспект
[FooAspect]
и на каком-то методе интерфейса есть такой же аспект, то будет примененен только аспект верхнего уровня; - С асинхронными методами нормально пока не работает. Доработки в планах;
- Ответственность за исключения внутри точек внедрения в вызов оригинального метода на разработчике. Если произойдет исключение в каком-то из обработчиков (OnEntry, OnSuccess, etc) аспекта
MethodBoundaryAspect
, то вызов основного метода будет прерван, а возникшее исключение попадет в вызывающий код. - Аспект
MethodInterceptionAspect
может быть использован только на методах. При этом на один метод разрешено иметь только один аспект данного типа, однако это не ограничивает использование данного типа аспекта совместно с аспектамиMethodBoundaryAspect
.
Для передачи состояния между вызовами в рамках одного аспекта используйте свойство IInvocationPipeline.AspectExecutionState
. Механизма для передачи состояния через весь пайплайн (между всеми аспектами) нет. Описанное выше свойство работает только на один аспект и другие аспекты изменений не увидят. Пример использования представлен тут.
- Если в процессе вызова исходного метода произошло исключение, то оно передается на вход аспекта в метод
OnException
. Если ни один аспект не установлен на метод - исключение с сохранением стека будет проброшено в вызывающий код. - Аспект может скрыть исключение вызвав любую из версий
pipeline.Return()
. В этом случае исключение будет поглощено и клиентский код продолжит свою работу как ни в чем не бывало. Однако, если ожидается какой-то результат выполнения метода, то возврат таким образом может привести к возникновениюNullReferenceException
. - Аспект может подменить исключение вызвав
pipeline.Throw(Exception)
, в этом случае старое исключение будет затерто, а клиентский код получит новое исключение, установленное обработчиком. - Если аспект не выполнит никаких действий по обработке исключений, то исключение будет передано на вход следующему аспекту. Если ни один из аспектов не остановил прокидывание исключения, то оно будет выброшено в клиентский код без потери стек-трейса.
- Если в процессе выполнения методов аспектов произойдет исключение, то оно будет выброшено во вне. Поэтому обработка исключений на уровне точек выполнения аспекта лежит на пользователях библиотеки.
Наиболее прозрачным образом библиотека работает с паре с фреймворками по внедрению зависимостей, так как они берут работу по инъекции аспектов в исходные классы на себя. Впрочем, библиотеку можно использовать и без контейнеров (с некоторыми органичениями).
По умолчанию выполняется связывание, если на интерфейсе или на каком-либо его методе установлен атрибут, который наследуется от MethodAspect
. Отключить применение аспектов на интерфейсе целиком или на отдельных методах можно, установив атрибут [SuppressWeaving]
. Так же можно установить отдельный атрибут-маркер, по которому будет определяться необходимость применения аспектов на тип (см. секцию Конфигурация).