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

вступление

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

Поскольку большинство разработчиков ежедневно следуют ООП, его принципы хорошо поняты и практикуются. Во время моего первого собеседования меня спросили об АОП - к сожалению, я смог только расширить его, но ничего больше. После того, как я вернулся домой (да, я получил эту работу, если вам интересно), я любезно попросил Google рассказать мне еще немного об АОП. После 5 минут чтения я уже понял. Вместо классов есть аспекты, и при вызове методов происходит некоторая магия. Еще 5 минут потратил на просмотр некоторых примеров, и я освоил это (да… желаю, чтобы это было правдой :)). «Если все так хорошо, как говорится, - подумал я, - почему я не научился этому раньше?» По сути, так началось и… закончилось мое путешествие по АОП. Некоторое время у меня не было необходимости возвращаться к этому, поскольку методы ООП были достаточно хороши, НО однажды ... давайте подробно рассмотрим это в следующем разделе.

Проблема

Недавно у меня появилась действительно простая задача. Я должен был реализовать логику, которая поможет мне собрать некоторые метрики вызовов для указанного набора методов. Кусок торта,
подумал я. Моя первоначальная идея была довольно простой:

  1. Создайте метрическую службу, которая будет обрабатывать сбор метрик.
  2. Внедрить метрическую службу в другие интересующие компоненты
  3. Для указанного набора методов добавьте строку, которая будет вызывать метрическую службу.

Я начал с создания метрической службы и с поиска мест, где можно было бы применить следующие шаги. Через 5 минут я был готов. Мне нужно было бы внести изменения в более чем 20 сервисов и вдвое больше методов. Все классы проходили тесты, которые тоже пришлось бы менять…

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

АОП

Прежде чем мы углубимся в основы АОП, давайте взглянем на этот пример метода:

Как следует из названия, этот метод отвечает за аутентификацию пользователя. Глядя на реализацию, мы легко можем выделить 3 основных обязанности.

  1. Регистрировать процесс аутентификации пользователя
  2. Сделайте фактическую аутентификацию
  3. В случае успеха post событие, в противном случае создайте соответствующую ошибку и выбросите

В мире ООП разработчики обычно делают такие цепочки функциональных возможностей. Даже если отдельные сервисы слабо связаны, в какой-то момент их нужно инкапсулировать в один класс, который создаст желаемую функциональность.

Цитата из одного из доступных определений парадигмы АОП:

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

Звучит очень мудро, но что это на самом деле означает?

Обеспокоенность в мире АОП определяет конкретную функциональность. На основе нашего примера метода аутентификации - каждая из указанных обязанностей может быть определена как проблема. Вы, наверное, спросите - значит, проблема - это просто другое название функциональности, которую включает в себя метод? - к счастью, нет.
Что действительно беспокоит, так это функциональность, которую можно применить к существующему методу, не изменяя его. Звучит немного удивительно? Давайте узнаем, как такого рода «волшебство» может быть достигнуто с помощью фреймворка Spring AOP.

Прежде чем мы начнем обсуждать мою проблему с метриками и ее решение с помощью АОП, мы должны выделить 4 фундаментальных элемента, а именно:

  • Точка соединения - поместите (вызов метода) в наше приложение, для которого мы будем применять функции проблем.
  • Pointcut - шаблон, определяющий одну или несколько точек соединения, к которым будет применена функция. Он определяется с помощью выражений (мы вернемся к ним через некоторое время)
  • Совет - определение фактического действия, которое будет выполнено для конкретной точки соединения. Существует пять типов советов: before, after returning, after throwing, after finally, around. потому что совет - это действие, прикрепленное к определенной точке соединения = ›вызов метода, совет название подсказывает, когда во время вызова метода выполняется желаемое действие (в соответствии с к возможным результатам выполнения метода). Первые четыре довольно очевидны. Вокруг - самый действенный вид совета (но мы вернемся к нему позже)
  • Аспект - базовая единица, охватывающая сквозные аспекты в мире АОП (для мира ООП класс - это эта единица). Он определяет pointcut и advices для соответствия системным требованиям. В среде Spring AOP аспекты представляют собой обычные классы, помеченные знаком @Aspect.

Имея это в виду, давайте взглянем на код.

Как мы помним, моей задачей было собрать некоторые метрики о вызовах конкретных методов. Я решил сделать два вида протоколирования метрик. Один будет отслеживать случаи, в которых метод выполняется нормально, а другой позаботится об ошибках. Итак, мы можем предположить, что наша метрическая служба могла бы выглядеть так:

В этом примере он просто регистрирует вызов метода со значением аргумента, переданного функции.

Поскольку аспект необходим для определения точек и советов, давайте создадим его и назовем его MetricsAspect. Вернемся к нашим требованиям. Метрики будут собираться для различных сервисов в приложении. Первое, что нам нужно сделать, это создать pointcut, который будет описывать выполнение желаемых методов. Поскольку эти методы могут быть помещены во множество разных классов, нам нужен способ их пометить. Самый простой способ - создать аннотацию и использовать ее для желаемых методов. Такая аннотация представлена ​​ниже:

Наш pointcut необходимо применить к выполнению методов, аннотированных с @LogMetric, имеющими возвращаемое значение типа SampleData. Pointcuts в Spring определяются методами, аннотированными @Pointcutconjuncted с выражениями pointcut. Наш pointcut будет:

В этом примере я использовал два выражения, связанных с логическим оператором И (другое также применимо). Первый - execution с аргументом:

@com.crustlab.aop.LogMetric com.crustlab.aop.model.SampleData *(..)

что означает: строгое выполнение методов, имеющих аннотацию @com.crustlab.aop.LogMetric, с возвращаемым значением типа SampleData, с любым именем * и с неопределенным набором аргументов (..). Чтобы лучше понять это выражение, давайте оценим pointcut для методов, возвращающих Long, имеющих имя метода, начинающееся с префикса find и имеющее единственный аргумент типа String. Это будет выглядеть так:

@Pointcut("execution(Long find*(String))")
private void longReturnFindsWithStringArgument() {
}

Пользовательские классы, используемые в определении pointcut, должны упоминаться по полному имени пакета!

Второе выражение - @annotation. Он используется для сопоставления методов, аннотированных указанным типом аннотации. Но разве вы еще не определили этот тип аннотации внутри execution выражения? - Да, у меня есть. Обычно это выражение используется следующим образом: @annotation(com.some.package.SomeAnnotationType)

Однако, если метод pointcut имеет аннотацию, переданную в качестве аргумента, такое определение: @annotation(annotationArgumentName) позволяет совету фиксировать значение аннотации для конкретного метода. В нашем случае нам нужно получить значение аннотации, поскольку она определяет имя метрики, которое метрическая служба будет использовать для регистрации. Доступны другие выражения pointcut. Для получения более подробной информации настоятельно рекомендуем вам прочитать официальную документацию.

Подготовив pointcut, нам нужно реализовать фактический механизм ведения журнала - advice. Как объяснялось, советы привязаны к типам выполнения методов. Поскольку наша метрическая служба предлагает методы для регистрации регулярного выполнения и выполнения ошибок, нам нужно создать два совета.
Давайте взглянем на них:

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

Первый метод использует совет AfterReturning, который выполняется после обычного выполнения метода. У аннотации есть два атрибута: один - это связанный pointcut (один или несколько, связанных с использованием логических операторов), который в данном случае равен logMetricAnnotatedPointcut(logMetric). Другой - argNames, который определяет имена атрибутов метода совета. В случае, когда есть только точка соединения в качестве атрибута, нет необходимости определять argNames, но мы получаем значение аннотации для pointcut и хотим, чтобы оно было передано методу advice, поэтому это необходимо. Второй метод использует AfterThrowing совет, и, как следует из названия, он вызывается, если выполнение метода заканчивается необработанным исключением.

Поскольку мы хотим, чтобы Spring внедрил MetricSevice в наш аспект, нам также необходимо добавить аннотацию @Component, чтобы автосоединение работало (, если вы используете @ComponentScan, вам все равно нужно использовать аннотацию bean-компонентов, поскольку @Aspect классы не представляют интереса ).
Файл конфигурации, который будет включать наш файл аспекта, должен иметь @EnableAspectJAutoProxy. Поскольку аспект - это обычный класс, а JoinPoint - интерфейс, логику внутри методов совета можно легко проверить.

Теперь давайте запустим код и проверим результаты консоли. При вызове нашего тестового сервиса foo() метод выводит следующие строки:

Работает как шарм! Ниже приведен журнал метода метания:

Похоже, наша проблема решена! Однако ранее я указывал еще на один тип советов под названием Вокруг. Давайте представим, что наши требования к сбору показателей изменились, и нам нужно не только отслеживать вызовы методов, но и регистрировать возвращаемое значение метода только при выполнении какого-либо условия. Здесь в игру вступает самый действенный совет. Его название предполагает, что действие запускается при выполнении метода - так когда именно? Под самым мощным я имел в виду, что использование этого совета дает нам возможность контролировать выполнение рекомендованного метода. Давайте посмотрим на простой пример:

честно говоря, желаемая функциональность может быть достигнута более простым способом, используя совет AfterRetuning с параметром returning, но я хотел показать вам использование совета Around;)

Единственное важное различие между предыдущими примерами - это тип точки соединения. ProceedingJoinPoint наследуется от JoinPoint интерфейса и добавляет
метод, который дает нам контроль над выполнением рекомендованного метода - proceed. Его вызов вызывает фактический вызов метода и возвращает его результат. Поскольку он должен поддерживать выполнение любого метода, он возвращает тип Object, который необходимо привести к правильному типу. В этом примере я просто выполняю простую проверку условий с возвращаемым значением и, в зависимости от ее результата, регистрирую метрику или нет, а затем возвращаю исходное значение. Возвращаемое значение `continue` - это фактический объект, возвращаемый рекомендуемым методом, поэтому этот тип совета позволяет нам возвращать значение, отличное от выполнения исходного метода.

Последнее, на что стоит обратить внимание, - это ограничения Spring AOP. На самом деле нужно помнить два момента:

  • Можно рекомендовать только непубличные методы, включенные в классы, управляемые Spring (службы, компоненты, прототипы и т. Д.).
  • Вызов рекомендованных методов внутри других методов того же компонента не вызовет действия рекомендации.

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