Spring: AOP

Spring: AOP


Основной задачей является устранение проблемы вызванной code tangling (запутанность) и code scattering (разбросаность). Tangling - запутанность бизнес логики и сложность технической реализаций; например, управление БД и бизнес взаимодействие с данными в ней в одном месте; Scattering - распределение/дублирование кода по в разных частях.

Для работы с AOP, Spring предалагает следующие библиотеки:

  • spring-aop - базовый API, включая компоненты для перехвата событий;
  • spring-aspect - интеграция с AspectJ;
  • spring-instrument - библиотека для интеграции с серверами приложений.

AOP понятия

  • Aspect - класс, содержащий механизм AOP. Для Spring - любой аннотированный @Aspec;
  • Weaving - связь между AOP объектом и другими объектами, реализациями или изменением поведения;
  • Join Point - точка использования AOP. Для Spring это метод выполнения;
  • Target Object - объект под воздействием AOP;
  • Target Method - метод под воздействием AOP;
  • Advice - действие, выполняемое в Join Point. Бывает:
    • Before Advice - метод, аннотированный @Before и который будет выполнен до Joint Point. Не мешает выполнению Target Method;
    • After Returning Advice - аннотирован @AfterReturning и выполняемый после “нормального” выполнения Target Method (без Exception);
    • After Throwing Advice - обратен After Returning Advice, выполняется после выбрасывания исключения (Exception). Аннотация @AfterThrowing;
    • After (finally) Advice - аннотирован @After и выполняется всегда после Target Method (вне заувисимости было исключение или нет);
    • Around Advice - аннотирован @Around. Позволяет выбрать поведение - выполнить Target Method? К объекту Target Object? Выбросить исключение? Вернуть результат? Или выбросить исключение?
  • Pointcut - указатель, идентифицирующий Join Point. Определяется посредством AspectJ Pointcut Expression Language либо в одной из аннотаций Advice группы (@{advice}, см. выше), либо в специальной аннотации @Pointcut;
  • Introduction - внедрение кода в Target Object. Использует @Declare* для внедрения методов, переменных и т.д.;
  • AOP Proxy - прокси объекты, которые служат обертками для Target Object, реализующими AOP

Для возможности использования AOP в Spring потребуется:

  • Наличие spring-aop и aspectweaver библиотек;
  • Создать бин (через @Component, @Bean, или иными способами) и добавить к нему аннотацию @Aspect;
  • Определить Advice Method - @Before, @After, …;
  • В @Configuration добавить включение AOP: @EnableAspectJAutoProxy. Возможно, с параметром proxyTargetClass=true;

AOP Expression Language

Шаблон для выражения AOP следующий:

1
execution_method([modifiers] [return_type] [class].[method]([arguments...]) throws [exception_class])

Некоторые особенности построения шаблонов:

  • Для класса можно использовать wildcard+, при которой поиск будет соответствовать указанному классу, а так же всем его подклассам;
  • Использование wildcard* осуществляет поиск даже среди произвольных символов: например, findBy* гарантирует нахождение findById, findBy_FirstName, findBy1st_name;
  • Ограничение within(com.mycompany.package.*) позволит ограничить поиск только одним пакетом;
  • Ограничение @annotation(com.mycompany.annotations.Limited) позволяет ограничивать поиск по аннотации;
  • Модификаторы доступа [modifiers] являются необязательными и в случае отсутствия заменяются на public;
  • Допустимо использование булевых логических условий типа && и ||. Например: execution(*.*MyRepo+.findBy*(...)) && within(my.company.pack).

Вынесенные Pointcuts

Иногда выражение может быть очень сложным:

1
execution_method(* com.mycompany.*.*.MyRepo+.findBy*(...)) || execution(* com.mycompany.objects.dao.*.*Service+.findBy*(String))

Такое выражение сложно прочитать, а так же оно является сложно переносимым (т.е. придется дублировать либо его целиком, либо какие-то части). Аннотация @Pointcut позволяет избежать подобных проблем:

1
2
3
4
5
6
7
8
@Before("repoExp() || serviceExp()")
public void doBefore(JoinPoint point) {...}

@Pointcut("execution(* com.mycompany.*.*.MyRepo+.findBy*(...))")
public void repoExp(){}

@Pointcut("execution(* com.mycompany.objects.dao.*.*Service+.findBy*(String))")
public void serviceExp(){}

В Advice аннотации (@Before) в примере выше можно указать класс, из которого следует брать Pointcut.

JoinPoint

Для получения полезной информации из Join Point можно:

1
2
3
Class targetClass = point.getSignature().getDeclatingType();
String method = point.getName();
Object[] args = point.getArgs();

Можно потребовать преобразовать и вытащить атрибуты по именам (требуется полное соответствие с сигнатурой):

1
2
@Pointcut("execution(...) && args(user) && target(service)")
public void before(User user, MyService service) {...}

Важно помнить, что args(...) применимо только к одному параметру. Если в методе их больше придется вытаскивать через getArgs().

Advice и их виды

  • @Before. Выполняется перед Target Method. В случае ошибки выполнения Advice, этот экземпляр Exception прокинется вызывающему методу, а Target Method вызван не будет;
  • @AfterReturning. Вызывается после успешного выполнения Target Method (без Exception). Может принимать параметр returning, в котором можно указать имя переменной, в которую следует передать результат выполнения Target Method:
1
2
@AfterReturning(value="execution(...)", returning="myResult")
public void after(JointPoint point, MyObject myResult) {...}
  • @AfterThrowing. Обратный предыдущему - выполняется только если Target Method выполнится с ошибкой:
1
2
@AfterThrowing(value="execution(...)", throwing="myException")
public void afterThrow(JointPoint point, Exception myException) {...}

Метод не(!) останавливается при ошибке, но благодаря throw new CustomException позволяет изменить выбрасываемое исключение (определяется в теле метода, аннотированного @AfterThrowing);

  • @After. Выполняется всегда, будь то Target Method завершен успешно, или выполнен с ошибкой;
  • @Around. Единственный Advice, который может повлиять на выполнение Target Method. Может полностью поменять поведение метода
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Around("execution(...)")
public Object around(ProceedingJoinPoint point) throws Throwable {
if(point.getSignature().getName().startesWith("...") {
return new Object();
} else {
Object targetResult = point.proceed();
if(targetResult == null) {
throw new Exception("...");
} else if(targetResult == DEFAULT) {
return targetResult;
} else {
return null;
}
}
}

Итог

Spring AOP мощный инструмент, но использовать его следует с осторожностью. Внедрение его в уже действующий код может быть дорогим, так что, лучше заранее определиться нужен ли он?

 Comments
Comment plugin failed to load
Loading comment plugin