Рубрики
Без рубрики

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

Краткий пример пользовательской аннотации Spring AOP

Автор оригинала: baeldung.

1. введение

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

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

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

2. Что такое аннотация AOP?

Чтобы быстро подвести итог, AOP означает аспектно-ориентированное программирование. По сути, это способ добавления поведения в существующий код без изменения этого кода .

Для подробного введения в AOP есть статьи о точечных разрезах AOP и советы . В этой статье предполагается, что у нас уже есть базовые знания.

Тип AOP, который мы будем реализовывать в этой статье, зависит от аннотаций. Возможно, мы уже знакомы с этим, если мы использовали пружину @Транзакционная аннотация:

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

Ключ здесь-неинвазивность. Используя метаданные аннотаций, наша основная бизнес-логика не загрязняется нашим кодом транзакций. Это облегчает рассуждение, рефакторинг и тестирование в изоляции.

Иногда люди, разрабатывающие весенние приложения, могут видеть это как Весеннюю магию’, не задумываясь подробно о том, как она работает. На самом деле то, что происходит, не особенно сложно. Однако, как только мы завершим шаги, описанные в этой статье, мы сможем создать нашу собственную пользовательскую аннотацию, чтобы понять и использовать AOP.

3. Зависимость Maven

Во-первых, давайте добавим наши зависимости Maven .

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


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



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

Обратите внимание, что мы включили стартер 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 bean для обнаружения. По сути, это класс, в котором мы будем реализовывать логику, которую мы хотим внедрить в аннотации наших клиентов.

6. Создание нашего Точечного рисунка и советов

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

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

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

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

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

Метод log Execution Time() сам по себе является нашим советом. Существует один аргумент 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;
}

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

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

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

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

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

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

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

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