Этот рассказ предлагает вам отличный способ изучить Аспектно-ориентированное программирование, изучив конкретные примеры. В частности, я продемонстрирую 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.