Реализация пользовательской аннотации Spring AOP

Реализация пользовательской аннотации Spring AOP

1. Вступление

В этой статье мы реализуем пользовательскую аннотацию АОП, используя поддержку АОП в Spring.

Во-первых, мы дадим общий обзор АОП, объясним, что это такое и его преимущества. После этого мы будем внедрять наши аннотации шаг за шагом, постепенно выстраивая более глубокое понимание концепций АОП по мере продвижения.

Результатом будет лучшее понимание АОП и возможность создавать наши собственные аннотации Spring в будущем.

2. Что такое аннотация АОП?

Чтобы быстро подвести итог, АОП означает аспектно-ориентированное программирование. По сути,it is a way for adding behavior to existing code without modifying that code.

Подробное введение в АОП можно найти в статьях по АОПpointcuts иadvice. В этой статье предполагается, что у нас уже есть базовые знания.

Тип АОП, который мы будем реализовывать в этой статье, основан на аннотациях. Возможно, мы уже знакомы с этим, если использовали аннотацию Spring@Transactional:

@Transactional
public void orderGoods(Order order) {
   // A series of database calls to be performed in a transaction
}

The key here is non-invasiveness. Благодаря использованию метаданных аннотаций наша основная бизнес-логика не загрязняется кодом транзакции. Это упрощает анализ, рефакторинг и тестирование в изоляции.

Иногда люди, разрабатывающие приложения Spring, могут видеть это какSpring Magic ', не задумываясь подробно о том, как это работает. На самом деле происходящее не представляет особой сложности. Однако, как только мы завершим шаги, описанные в этой статье, мы сможем создать нашу собственную аннотацию для понимания и использования АОП.

3. Maven Dependency

Сначала добавим нашMaven dependencies.

В этом примере мы будем использовать Spring Boot, поскольку подход, основанный на соглашении, а не конфигурации, позволяет нам приступить к работе как можно быстрее:


    org.springframework.boot
    spring-boot-starter-parent
    1.5.2.RELEASE



    
        org.springframework.boot
        spring-boot-starter-aop
    

Обратите внимание, что мы включили стартер АОП, который извлекает библиотеки, необходимые для реализации аспектов.

4. Создание нашей собственной аннотации

Мы создадим аннотацию, которая будет использоваться для регистрации количества времени, которое требуется для выполнения метода. Давайте создадим нашу аннотацию:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {

}

Несмотря на относительно простую реализацию, стоит отметить, для чего используются две метааннотации.

Аннотация@Target сообщает нам, где будет применяться наша аннотация. Здесь мы используемElementType.Method,, что означает, что он будет работать только с методами. Если бы мы попытались использовать аннотацию где-либо еще, наш код не скомпилировался бы. Такое поведение имеет смысл, так как наша аннотация будет использоваться для регистрации времени выполнения метода.

А@Retention просто указывает, будет ли аннотация доступна JVM во время выполнения или нет. По умолчанию это не так, поэтому Spring AOP не сможет увидеть аннотацию. Вот почему его изменили.

5. Создание нашего аспекта

Теперь у нас есть аннотация, давайте создадим наш аспект. Это всего лишь модуль, который инкапсулирует нашу междисциплинарную задачу, в нашем случае это регистрация времени выполнения метода. Все это класс, помеченный@Aspect:

@Aspect
@Component
public class ExampleAspect {

}

Мы также включили аннотацию@Component, так как наш класс также должен быть компонентом Spring для обнаружения. По сути, это класс, в котором мы будем реализовывать логику, которую мы хотим внедрить в нашу пользовательскую аннотацию.

6. Создание нашего Pointcut и Advice

А теперь давайте создадим нашу точку и совет. Это будет аннотированный метод, который живет в нашем аспекте:

@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    return joinPoint.proceed();
}

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

Во-первых, мы аннотировали наш метод с помощью@Around.. Это наш совет, а пример «совета» означает, что мы добавляем дополнительный код как до, так и после выполнения метода. Есть и другие типы советов, такие какbefore иafter, но они не будут рассматриваться в этой статье.

Затем наша аннотация@Around имеет аргумент сечения точки. Наш pointcut просто говорит: «Применяйте этот совет к любому методу, помеченному@LogExecutionTime». Есть много других типов pointcut, но они снова будут исключены, если scope.

Сам методlogExecutionTime() - это наш совет. Есть единственный аргументProceedingJoinPoint.. В нашем случае это будет выполняемый метод, помеченный@LogExecutionTime..

Наконец, когда наш аннотированный метод заканчивается вызовом, сначала произойдет наш совет. Тогда нам решать, что делать дальше. В нашем случае наш совет не делает ничего, кроме вызоваproceed(),, который является простым вызовом исходного аннотированного метода.

7. Регистрация времени выполнения

Теперь у нас есть скелет, все, что нам нужно сделать, это добавить дополнительную логику к нашему совету. Это будет то, что записывает время выполнения в дополнение к вызову исходного метода. Давайте добавим это дополнительное поведение к нашему совету:

@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();

    Object proceed = joinPoint.proceed();

    long executionTime = System.currentTimeMillis() - start;

    System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
    return proceed;
}

Опять же, мы не сделали здесь ничего особенно сложного. Мы только что записали текущее время, выполнили метод, а затем распечатали количество времени, которое потребовалось на консоли. Мы также регистрируем подпись метода, которая предназначена для использования экземпляраjoinpoint. Мы также сможем получить доступ к другим частям информации, если захотим, например к аргументам метода.

Теперь давайте попробуем аннотировать метод с помощью@LogExecutionTime,, а затем выполнить его, чтобы посмотреть, что произойдет. Обратите внимание, что это должен быть Spring Bean для правильной работы:

@LogExecutionTime
public void serve() throws InterruptedException {
    Thread.sleep(2000);
}

После выполнения мы должны увидеть в консоли следующее:

void org.example.Service.serve() executed in 2030ms

8. Заключение

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

Исходный код нашего приложения доступен наover on GitHub; это проект Maven, который должен работать как есть.