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

Одним из ключевых компонентов Spring является АОП-фреймворк. Хотя IoC-контейнер Spring не зависит от АОП (то есть вам не нужно использовать АОП, если вы этого не хотите), АОП дополняет Spring IoC, что обеспечивает получение очень адекватного решения для промежуточного ПО.

Spring AOP с использованием срезов из AspectJ

Spring предлагает простые и полнофункциональные способы написания специальных аспектов с использованием или подхода на основе схем, или стиля аннотаций @AspectJ. Оба этих стиля поддерживают полностью типизированные Advice и возможность использования языка срезов AspectJ, но при этом используют Spring AOP для привязывания.

В этой главе рассмотрена поддержка АОП на основе схем и @AspectJ.

АОП используется в Spring Framework для:

  • Предоставления декларативных корпоративных служб. Наиболее важной такой службой является декларативное управление транзакциями.

  • Позволяет пользователям реализовывать специальные аспекты, дополняя использование ООП с помощью АОП.

Если вас интересуют только общие декларативные службы или другие не требующие подготовки декларативные службы промежуточного ПО, такие как пулинг, то вам не придется работать непосредственно с Spring AOP, поэтому вы смело можете пропустить большую часть этой главы.

Понятия АОП

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

  • Аспект (aspect): Модульно организованная функциональность (concern), которая охватывает несколько классов. Управление транзакциями является хорошим примером сквозной функциональности в корпоративных Java-приложениях. В Spring AOP аспекты реализуются с помощью обычных классов (подход на основе схем) или обычных классов, аннотированных аннотацией @Aspect (стиль @AspectJ).

  • Точка соединения (joint point): Точка во время выполнения программы, например, выполнение метода или обработка исключения. В Spring AOP точка соединения всегда представляет собой выполнение метода.

  • Advice: Действие, предпринимаемое аспектом в определенной точке соединения. Различные виды Advice включают Advice "вместо (around)", "перед (before)" и "после (after)". (Типы Advice будут рассмотрены дальше). Многие АОП-фреймворки, включая Spring, моделируют Advice как перехватчик и поддерживают цепочку перехватчиков вместо точки соединения.

  • Срез (pointcut): Предикат, который соответствует точкам соединения. Advice связан с выражением среза и выполняется в любой точке соединения, совпадающей со срезом (например, выполнение метода с определенным именем). Концепция точек соединения, сопоставленных с выражениями среза, является центральной в АОП, а Spring по умолчанию использует язык выражений срезов AspectJ.

  • Введение (introduction): Объявление дополнительных методов или полей от имени типа. Spring AOP позволяет вам внедрять новые интерфейсы (и соответствующую реализацию) в любой снабженный Advice-ом объект. Например, можно использовать введение, чтобы в принудительном порядке бин реализовывал интерфейс IsModified для упрощения кэширования. (В сообществе AspectJ введение известно как межтиповое объявление).

  • Целевой объект (Target object): Объект, который снабжается Advice-ом по одному или нескольким аспектам. Также называется "снабжаемый Advice-ом объект". Поскольку Spring AOP реализован с использованием динамических прокси, этот объект всегда является проксированным объектом.

  • Прокси АОП: Объект, создаваемый АОП-фреймворком для реализации аспектных контрактов (снабжает Advice-ом выполнение методов и так далее). В Spring Framework прокси АОП – это динамический прокси JDK или прокси CGLIB.

  • Связывание (weaving): связывание аспектов с другими типами приложений или объектами для создания снабженного советом объекта. Оно может быть произведено во время компиляции (например, с помощью компилятора AspectJ), во время загрузки или во время выполнения программы. Spring AOP, как и другие чистые АОП-фреймворки на Java, осуществляет связывание во время выполнения программы.

Spring AOP включает в себя следующие типы советов:

  • Совет "перед (before)": Совет, который выполняется перед точкой соединения, но не имеет возможности предотвратить поток выполнения, идущий к точке соединения (если только он не генерирует исключение).

  • Совет "после возврата (after returning)": Совет, который будет выполняться после нормального завершения работы точкой соединения (например, если метод возвращается без генерации исключения).

  • Совет "после генерации исключения (throwing)": Совет, который будет выполняться, если метод завершает работу, генерируя исключение.

  • Совет "после (окончательно) (after (finally)": Совет должен выполняться независимо от способа выхода из точки соединения (обычным или исключительным возвратом).

  • Совет "вместо (around)": Совет, который окружает точку соединения, такую как, например, вызов метода. Это самый действенный вид совета. Совет "вместо" может следовать специальной логике работы до и после вызова метода. Он также отвечает за выбор того, следует ли перейти к точке соединения или сократить выполнение рекомендованного метода, вернув собственное возвращаемое значение или генерируя исключение.

Совет "вместо" – это самый обобщенный вид совета. Поскольку Spring AOP, как и AspectJ, предоставляет полный спектр типов советов, мы рекомендуем использовать наименее влиятельный тип совета, который может реализовать требуемую логику работы. Например, если вам нужно лишь обновить кэш с возвращаемым значением метода, то лучше реализовать совет "после возврата", чем совет "вместо", хотя совет "вместо" и может делать то же самое. Использование наиболее конкретного типа совета обеспечивает более простую модель программирования с меньшей вероятностью возникновения ошибок. Например, не нужно вызывать метод proceed() для JoinPoint, используемый для совета "вместо", и, следовательно, у вас не получится не вызвать его.

Все параметры советов статически типизированы, поэтому вы работаете с параметрами советов соответствующего типа (например, типа возвращаемого значения при выполнении метода), а не с массивами Object.

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

Возможности и цели Spring AOP

Spring AOP реализован на чистом Java. Нет необходимости в специальном процессе компиляции. Spring AOP не требует управления иерархией загрузчика классов и поэтому подходит для использования в контейнере сервлетов или на сервере приложений.

Spring AOP в настоящее время поддерживает только точки соединения выполнения методов (снабжение советом выполнения методов для бинов Spring). Перехват полей не реализован, хотя поддержка перехвата полей может быть добавлена без нарушения основных API-интрефейсов Spring AOP. Если необходимо снабжать советом доступ к полям и обновлять точки соединения, рассмотрите такой язык, как AspectJ.

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

Так, например, функциональность АОП в Spring Framework обычно используется в сочетании с IoC-контейнером Spring. Аспекты конфигурируются с помощью обычного синтаксиса определения бинов (хотя это позволяет использовать высокопроизводительные возможности "автопроксирования"). Это принципиальное отличие от других реализаций АОП. У вас не получится легко и эффективно делать некоторые вещи с помощью Spring AOP, например, снабжать советом крайне мелкомудльные объекты (как правило, объекты предметной области). AspectJ - лучший выбор в таких случаях. Тем не менее, наш опыт показывает, что Spring AOP обеспечивает отличное решение большинства проблем в корпоративных Java-приложениях, которые поддаются АОП.

Spring AOP ни в коем случае не пытался конкурировать с AspectJ в части предоставления комплексного решение для АОП. Мы считаем, что и прокси-ориентированные фреймворки, такие как Spring AOP, и полноценные фреймворки, такие как AspectJ, являются ценными и скорее дополняют друг друга, чем конкурируют. Spring легко интегрирует Spring AOP и IoC с AspectJ, что позволяет использовать АОП в рамках согласованной архитектуры приложений на базе Spring. Эта интеграция не влияет на API-интерфейс Spring AOP или API-интерфейс AOP Alliance . Spring AOP остается обратно совместимым.

Одним из основных принципов Spring Framework является неагрессивность. Идея заключается в том, чтобы не принуждать вас внедрять специфичные для фреймворка классы и интерфейсы в свою бизнес-модель или модель предметной области. Однако в некоторых моментах Spring Framework дает возможность внедрить в вашу кодовую базу специфические для Spring Framework зависимости. Смысл предоставления таких возможностей заключается в том, что в определенных сценариях может быть просто проще прочитать или закодировать определенную функциональность таким образом. Однако Spring Framework (почти) всегда предлагает выбор: У вас есть возможность принять взвешенное решение о том, какой вариант лучше всего подходит для вашего конкретного случая или сценария использования.

Один из таких выборов, который имеет отношение к этой главе, это выбор АОП-фреймворка (и стиля АОП). Имеется такой выбор: AspectJ, Spring AOP или оба варианта. У вас также есть выбор: либо подход в стиле аннотации @AspectJ, либо подход в стиле конфигурации Spring XML. Тот факт, что в этой главе мы решили сначала представить подход в стиле @AspectJ, не следует воспринимать как признак того, что команда Spring отдает подходу в стиле аннотации @AspectJ большее предпочтение, чем подходу в стиле конфигурации Spring XML.

Прокси АОП

Spring AOP по умолчанию использует стандартные динамические прокси JDK для прокси АОП. Это позволяет проксировать любой интерфейс (или набор интерфейсов).

Spring AOP также может использовать прокси CGLIB. Они необходимы для проксирования классов, а не интерфейсов. По умолчанию, CGLIB используется, если бизнес-объект не реализует интерфейс. Поскольку рекомендуется программировать интерфейсы, а не классы, бизнес-классы обычно реализуют один или несколько бизнес-интерфейсов. Можно принудительно использовать CGLIB в тех (надеемся, редких) случаях, когда необходимо снабдить советом метод, не объявленный в интерфейсе, или когда нужно передать методу проксированный объект в качестве конкретного типа.

Важно понимать, что Spring AOP основывается на прокси.