Этот рассказ предлагает вам отличный способ изучить Аспектно-ориентированное программирование, изучив конкретные примеры. В частности, я продемонстрирую SpringBoot AOP, реализовав 4 аспекта.

Содержание:

  • Что такое аспект?
  • @Cacheable: стандартный совет Spring.
  • Журнал вызовов REST (с настраиваемым аспектом)
  • Мониторинг производительности (с AOP)
  • Механизм повтора (с AOP)

Если вы тот человек, который хочет пропустить длинные описания и просто взглянуть на конкретный код, я вам помогу:



Что такое аспект?

Так что есть несколько отличных ресурсов для обзора Spring AOP, включая эту статью о Baeldung и официальную документацию Spring AOP. Но поскольку мы не хотим сосредотачиваться на скучной теории, а, скорее, придерживаться практики, вот действительно краткое описание того, как работает АОП:

В этом руководстве нам потребуются следующие термины:

  • Совет: метод, реализующий некоторые общие задачи, такие как ведение журнала или кеширование.
  • Pointcut: шаблонное выражение, которое соответствует местам, в которых следует вызывать совет.
  • Аспект: совет плюс выражение Pointcut
  • Бонус - точка соединения: все места в вашем коде, которые представляют кандидатов на Pointcut.

@Cacheable: стандартный совет Spring

Начнем с простого и рассмотрим уже реализованный Совет от Spring, а именно аннотацию @Cacheable. Допустим, ваш веб-сервис должен вычислять числа серии Фибоначчи.

Если вы не знаете, что такое ряд Фибоначчи: это ряд, начинающийся с 0 и 1, и каждое последующее число является суммой двух предыдущих чисел.

Мы реализуем вычисление Фибоначчи в классе @Service:

Далее мы используем этот класс обслуживания в нашем контроллере REST:

Наша реализация рекурсивная и поэтому довольно медленная. Так как же сделать ваш веб-сервис быстрее? Один из способов - использовать более быстрый алгоритм, но давайте решим проблему с помощью функции Spring @Cacheable. Эта аннотация создает кеш в фоновом режиме, где сохраняются все предыдущие результаты. Все, что нам нужно сделать, это добавить аннотацию @Cacheable к нашему методу:

Теперь мы готовы протестировать наш механизм кэширования, отправив запрос REST на http://localhost:8080/api/fibonacci/40. Я попытался вычислить 40-й уровень Фибоначчи на собственном ноутбуке и вот результаты:

  • Первый вызов REST: 1902 мс
  • Второй вызов REST: 1 мс

Довольно хороший результат эйы🤙😎

И последнее, что я хотел бы упомянуть: чтобы активировать функцию кеширования Spring, вам нужно добавить @EnableCaching в класс @Configuration.

Журнал вызовов REST с настраиваемым аспектом

Это было довольно просто, правда? Итак, давайте перейдем к более сложному варианту использования: теперь мы создаем настраиваемый аспект!

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

Первая строка определяет Выражение точки, а последующий метод представляет Совет. Давайте разберем эти две части по очереди:

Pointcut:
Выражение Pointcut определяет места, в которые вставляется наш совет. В нашем случае Аспект применяется перед каждым методом с аннотацией @LogMehtodName. Обратите внимание, что @LogMethodName - это наша настраиваемая аннотация, которую мы используем в качестве маркера Pointcut.

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

При наличии нашего Аспекта необходимы три дополнительные строки кода, чтобы все работало:

  • Сначала добавляем маркер @LogMethodName в наш fibonacci() метод
  • Во-вторых, мы должны добавить @Aspect к классу, содержащему наш аспект
  • В-третьих, включите сканирование аспектов Spring с @EnableAspectJAutoProxy в любом @Configuration классе.

Вот и все, мы реализовали собственный Совет! Давайте проведем тест! Мы запускаем REST-запрос к веб-сервису для вычисления 40-го числа Фибоначчи и смотрим на вывод консоли:

Method [fibonacci] gets called with parameters [40]

Само собой разумеется, что такие сообщения журнала будут большим подспорьем, если вам когда-нибудь понадобится отслеживать ошибки в вашем приложении.

Мониторинг производительности с помощью АОП

В предыдущем примере мы использовали выражение Pointcut типа @Before - здесь Advice запускается перед фактическим методом. Давайте переключимся и реализуем Pointcut @Around. Такой совет запускается частично перед целевым методом и частично после него.

Теперь наша цель - отслеживать время выполнения любого вызова REST. Давайте продолжим и реализуем требование мониторинга в обобщенном виде, а именно Аспект:

Pointcut:
Как и раньше, мы создаем новую пользовательскую аннотацию @MonitorTime для маркировки наших Pointcut.

Совет:
@Around Аспект должен иметь аргумент типа ProceedingJoinPoint. Этот тип имеет proceed() метод, который запускает выполнение фактического целевого метода. Итак, в нашем Совете мы сначала запрашиваем текущее время в миллисекундах. После выполнения целевого метода мы снова измеряем текущее время, и оттуда мы можем вычислить разницу во времени.

Давайте продолжим и отметим наш целевой метод аннотацией @MonitorTime:

К настоящему времени к нашему методу REST прикреплено несколько маркеров Pointcut😉 В любом случае, давайте продолжим и протестируем нашу функцию мониторинга производительности. Как и раньше, вычисляем 40-е число Фибоначчи:

Method [fibonacci] gets called with parameters [40]
Execution took [1902ms]

Как видите, этот конкретный вызов REST занял 1902 мс. С этим @Around аспектом вы определенно станете продвинутым программистом АОП! 💪

Механизм повтора с AOP

Распределенные системы могут испытывать проблемы с параллелизмом. Одним из таких примеров может быть ситуация, когда два экземпляра веб-службы одновременно пытаются получить доступ к одной и той же записи в базе данных. Часто такую ​​проблему с блокировкой можно решить, повторив операцию. Единственное требование здесь - это идемпотентность операции.

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

Pointcut:
Наш совет работает с любым методом с пользовательской аннотацией @RetryOperation.

Совет:
В операторе try мы запускаем целевой метод. Этот метод может вызвать ошибку RuntimeException. Если это произойдет, мы увеличиваем счетчик numAttempts и просто повторно запускаем целевой метод. Как только целевой метод завершится успешно, мы выходим из Advice.

В демонстрационных целях создадим метод REST для хранения String. Этот метод дает сбой в 50% случаев:

Благодаря нашей аннотации @RetryOperation, вышеупомянутый метод будет повторяться до тех пор, пока не будет успешным. Кроме того, мы используем аннотацию @LogMethodName, чтобы видеть каждый вызов метода. Давайте продолжим и протестируем нашу новую конечную точку REST; для этого мы отправляем запрос REST к http://localhost:8080/api/storeData?data=hello-world.

Method [storeData] gets called with parameters [hello-world]
Method [storeData] gets called with parameters [hello-world]
Method [storeData] gets called with parameters [hello-world]
Pretend everything went fine

В приведенном выше случае операция завершилась неудачно 2 раза и завершилась успешно только с третьей попытки.

Заключение

Поздравляю, теперь вы профессиональный программист АОП🥳🚀 Вы можете найти полностью рабочий веб-сервис со всеми аспектами в моем репозитории на Github:



Большое спасибо за чтение, пожалуйста, оставьте комментарий, если у вас есть какие-либо вопросы или отзывы! 😄

Впервые опубликовано на DEV.to.