Что такое aop spring
Русские Блоги
5. Aspect Oriented Programming with Spring
АОП предоставляет другой способ мышления программной структуры в дополнение к ООП; структура АОП является ключевым компонентом весной, но Spring не полагается на АОП и может использоваться, АОП дополняет IOC и предоставляет очень мощные решения промежуточного программного обеспечения
5.1. AOP Concepts
Определение ключевого слова:
Spring AOP содержит советы следующих типов:
В Spring 2.0 все параметры уведомления статически типизированы, поэтому вы можете использовать соответствующий тип параметра Advice (например, тип возвращаемого значения выполнения метода) вместо массива Object.
5.2. Spring AOP Capabilities and Goals
Spring AOP в настоящее время поддерживает только точки подключения для выполнения методов (рекомендуется выполнять методы в компонентах Spring). Хотя можно добавить поддержку перехвата полей без нарушения работы ядра Spring AOP API, перехват полей не реализован. Если вам нужно предложить доступ к полям и обновить точки подключения, рассмотрите возможность использования таких языков, как AspectJ.
Метод АОП Spring AOP отличается от метода большинства других фреймворков АОП. Цель не состоит в том, чтобы предоставить наиболее полную реализацию АОП (хотя Spring AOP очень мощный инструмент). Вместо этого цель состоит в том, чтобы обеспечить тесную интеграцию между реализациями АОП и Spring IoC, чтобы помочь решить общие проблемы в корпоративных приложениях. Функция AOP Spring Framework обычно используется с контейнером Spring IoC для настройки аспектов с использованием обычного синтаксиса определения bean (хотя это позволяет использовать мощные возможности «автоматического прокси»).
Spring легко интегрирует Spring AOP и IoC с AspectJ для достижения всех видов использования AOP в согласованной архитектуре приложения на основе Spring.
5.3. AOP Proxies
Spring AOP по умолчанию использует стандартный динамический прокси JDK для прокси AOP. Это позволяет проксировать любой интерфейс (или набор интерфейсов).
Spring AOP также может использовать прокси CGLIB. Это требуется для прокси-классов, а не интерфейсов. По умолчанию, если бизнес-объект не реализует интерфейс, используется CGLIB. Вы также можете заставить это
5.4. @AspectJ support
Spring использует библиотеку, предоставленную AspectJ, для объяснения тех же аннотаций, что и AspectJ 5, для анализа точек и сопоставления. Однако среда выполнения AOP по-прежнему является чистой Spring AOP и не зависит от компилятора AspectJ или Weaver.
5.4.1. Enabling @AspectJ Support
Вы можете использовать конфигурацию в стиле XML или Java, чтобы включить поддержку @AspectJ. В любом случае вам также необходимо убедиться, что библиотека AspectJ AspectJweaver.jar находится в пути к классам приложения (версия 1.8 или выше).
Открыть поддержку @AspectJ двумя способами
5.4.2. Declaring an Aspect
Если в компоненте существует аннотация @Aspect, Spring автоматически обнаружит ее и использует для настройки Spring AOP.
Аспект (то есть аннотированный класс) такой же, как и у обычного класса, но также включает в себя объявления pointcut, advice и Introduction (межтиповые).
5.4.3. Declaring a Pointcut
Объявить точку отсечения
Spring AOP ограничивает сопоставление только с точками подключения выполнения метода
Spring AOP поддерживает следующие индикаторы Pointcut (PCD) AspectJ для выражений pointcut:
Полная поддержка языка Pointcut AspectJ не поддерживает дополнительные индикаторы pointcut в Spring: call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution, insidecode, cflow, cflowbelow, if, @this и @withincode. Использование этих индикаторов pointcut в выражениях pointcut, интерпретируемых Spring AOP, вызовет выброс исключения IllegalArgumentException.
Spring AOP также поддерживает еще один компонент с именем PCD. Этот PCD позволяет ограничить соответствие точек подключения конкретному именованному компоненту Spring или набору именованных компонентов Spring (при использовании подстановочных знаков). BeanPCD имеет следующий вид:
bean(idOrNameOfBean)
Токен idOrNameOfBean может быть именем любого компонента Spring. Предоставляет ограниченную поддержку подстановочных знаков с использованием символа *, поэтому, если вы установили некоторые соглашения об именах для компонентов Spring, вы можете написать выражения beanPCD для их выбора. Как и другие индикаторы pointcut, beanPCD также можно использовать с операторами && (и), || (или) и! (Отрицание).
Выражение точки отсечки
Диапазон соответствия:
kinded: выполнение, получение, установка, вызов и обработчик.
область видимости: внутри и внутри кода
контекстный: this, target и @annotation
5.4.4. Declaring Advice
Параметры совета
org.aspectj.lang.JoinPoint
Прокси-объект (this), целевой объект (цель) и описание (@within, @target, @annotation и @args) могут быть объединены аналогичным образом.
Определите имя параметра:
Advice Ordering
Когда несколько советов соответствуют точке подключения, по умолчанию порядок отсутствует, вы можете использовать org.springframework.core.Ordered, чтобы указать приоритет
5.4.5. Introductions
5.4.6. Aspect Instantiation Models
Вы можете объявить аспекты, указав пункты в аннотации @Aspect через perthis
5.4.7. An AOP Example
Пример: для высокого уровня параллелизма вызов службы может сообщать об ошибке, и он может быть успешным, если повторить попытку; для этого типа службы, которую необходимо повторить, ее можно рассматривать как обходную реализацию
5.5. Schema-based AOP Support
Для использования этой функции требуется пружинная опора.
5.5.1. Declaring an Aspect
5.5.2. Declaring a Pointcut
Объявить точку отсечения
5.5.3. Declaring Advice
Параметры: несколько параметров можно разделять запятыми.
5.5.4. Introductions
5.5.5. Aspect Instantiation Models
Экземплярные модели в настоящее время поддерживают только синглтон
5.5.6. Advisors
5.5.7. An AOP Schema Example
5.6. Choosing which AOP Declaration Style to Use
5.6.1. Spring AOP or Full AspectJ?
5.6.2. @AspectJ or XML for Spring AOP?
Стиль XML имеет два недостатка: 1. Он не может полностью инкапсулировать реализацию требований, которые он обрабатывает, в одном месте. Принцип DRY считает, что любая часть знания в системе должна иметь единое, ясное и авторитетное выражение. При использовании стиля XML знания о том, как реализовать требования, разделяются между классом вспомогательного компонента в файле конфигурации и объявлением XML. Когда вы используете стиль @AspectJ, эта информация инкапсулируется в одном модуле :. 2. Стиль XML немного более ограничен, чем стиль AspectJ, с точки зрения выражения содержимого: он поддерживает только «одноэлементную» модель создания экземпляров и не может объединять именованные точки входа, объявленные в XML.
5.7. Mixing Aspect Types
Используя автоматическую поддержку прокси, определение режимаaop:aspectаспектaop:advisorОбъявленные советники, даже используя определенные в стиле Spring 1.2 прокси и перехватчики в одной конфигурации, могут полностью смешивать аспекты стиля @AspectJ.
5.8. Proxying Mechanisms
Если целевой объект, который будет прокси, реализует хотя бы один интерфейс, используйте динамический прокси JDK. Все интерфейсы, реализованные целевым типом, являются прокси. Если целевой объект не реализует какой-либо интерфейс, будет создан прокси CGLIB.
Если вы хотите принудительно использовать прокси CGLIB (например, проксировать каждый метод, определенный для целевого объекта, а не только те методы, которые реализованы в его интерфейсе), вы можете это сделать. Однако вам следует рассмотреть следующие вопросы:
5.8.1. Understanding AOP Proxies
Spring aop реализован на основе прокси. Когда вызываются собственные методы, например, используя this для вызова другого метода, на этот раз не используется для получения прокси; AspectJ не основан на платформе aop на основе прокси.
Значение такого подхода:
It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.
5.9. Programmatic Creation of @AspectJ Proxies
В дополнение к использованиюaop:configпротивaop:aspectj-autoproxyВы также можете программно создать прокси, который информирует целевой объект
Интерфейс org.springframework.aop.aspectj.annotation.AspectJProxyFactory может создавать прокси для одного или нескольких @AspectJ
5.10. Using AspectJ with Spring Applications
Требуется пакет spring-sizes.jar; используйте компилятор AspectJ или weaver вместо Spring AOP, если ваши требования превышают функции, предоставляемые Spring AOP
5.10.1. Using AspectJ to Dependency Inject Domain Objects with Spring
Spring-sizes.jar содержит аспект, управляемый аннотациями, который позволяет внедрять зависимости любого объекта с этой функцией. Эта поддержка предназначена для объектов, созданных вне контроля какого-либо контейнера. Объекты домена обычно попадают в эту категорию, потому что они обычно создаются программным способом оператором new или инструментами ORM из-за запросов к базе данных.
В следующих главах рассказывается, как использовать аспекты
Знакомство с АОП
Парадигмы программирования
В современном мире IT-разработки существует довольно большое множество различных подходов к написанию программ. Так, например, кому-то нравиться представлять программу в виде последовательности действий, а кто-то считает, что программа должна представлять собой множество объектов, общающихся друг с другом. Совокупности этих идей и понятий образуют своего рода стиль написания программы, который принято назвать – парадигма программирования.
В этой статье я хочу рассказать о сравнительно молодой, но крайне, на мой взгляд, полезной парадигме программирования – аспектно-ориентированном программировании.
Основы АОП
public BookDTO getBook(Integer bookId) <
BookDTO book = bookDAO.readBook(bookId);
return book;
>
public BookDTO getBook(Integer bookId) <
LOG.debug( «Call method getBook with id » + bookId);
BookDTO book = bookDAO.readBook(bookId);
LOG.debug( «Book info is: » + book.toString());
return book;
>
public BookDTO getBook(Integer bookId) throws ServiceException <
LOG.debug( «Call method getBook with id » + bookId);
BookDTO book = null ;
try <
book = bookDAO.readBook(bookId);
> catch(SQLException e) <
throw new ServiceException(e);
>
LOG.debug( «Book info is: » + book.toString());
return book;
>
public BookDTO getBook(Integer bookId) throws ServiceException, AuthException <
if (!SecurityContext.getUser().hasRight(«GetBook»))
throw new AuthException(«Permission Denied»);
LOG.debug( «Call method getBook with id » + bookId);
BookDTO book = null ;
try <
book = bookDAO.readBook(bookId);
> catch (SQLException e) <
throw new ServiceException(e);
>
LOG.debug( «Book info is: » + book.toString());
return book;
>
public BookDTO getBook(Integer bookId) throws ServiceException, AuthException <
if (!SecurityContext.getUser().hasRight( «GetBook» ))
throw new AuthException( «Permission Denied» );
LOG.debug( «Call method getBook with id » + bookId);
BookDTO book = null ;
String cacheKey = «getBook:» + bookId;
try <
if (cache.contains(cacheKey)) <
book = (BookDTO) cache.get(cacheKey);
> else <
book = bookDAO.readBook(bookId);
cache.put(cacheKey, book);
>
> catch (SQLException e) <
throw new ServiceException(e);
>
LOG.debug( «Book info is: » + book.toString());
return book;
>
Можно продолжать совершенствовать данный метод, но для начала — достаточно. В ходе наших доработок мы получили метод в 10 раз (с 2 до 20 LOC) превышающий исходный размер. Самое интересное, что объём бизнес-логики в нём не изменился – это всё та же 1 строка. Остальной код реализует некоторую общую служебную функциональность приложения: логирование, обработку ошибок, проверку прав доступа, кеширование и так далее.
Пример использования (AspectJ)
AspectJ является аспектно-ориентированным расширением/framework’ом для языка Java. На данный момент это, пожалуй, самый популярный и развивающийся АОП движок.
Рассмотрим реализацию аспекта логирования с его помощью:
@Aspect
public class WebServiceLogger <
private final static Logger LOG =
Logger.getLogger(WebServiceLogger. class );
@Pointcut( «execution(* example.WebService.*(..))» )
public void webServiceMethod()
@Pointcut( «@annotation(example.Loggable)» )
public void loggableMethod()
LOG.debug( «Call method » + methodName + » with args » + methodArgs);
Object result = thisJoinPoint.proceed();
LOG.debug( «Method » + methodName + » returns » + result);
Первым делом создаётся аспект логирования методов сервисов – класс WebServiceLogger, помеченный аннотацией Aspect. Далее определяются два среза точек соединения: webServiceMethod (вызов метода, принадлежащего классу WebService) и loggableMethod (вызов метода, помеченного аннотацией @Loggable). В завершении объявляется совет (метод logWebServiceCall), который выполняется вместо (аннотация Around) точек соединения, удовлетворяющих срезу («webServiceMethod() && loggableMethod()»).
В коде совета происходит получение информации о текущем методе (точке соединения), логирование начала выполнения метода, непосредственный вызов запрошенного метода, логирование и возвращение результата работы.
Для того, что бы использовать аспекты AspectJ их придётся скомпилировать и «вшить» в основные классы с помощью специального компилятора AJC.
Продукт бесплатный. Распространяется под Eclipse License.
Пример использования (PostSharp)
Рассмотрим, как с помощью него описать аспект обработки исключений. Первым делом необходимо создать класс, расширяющий соответствующий аспект:
public class ExceptionDialogAttribute : OnExceptionAspect
<
public override void OnException(MethodExecutionEventArgs eventArgs)
<
string message = eventArgs.Exception.Message;
Window window = Window.GetWindow((DependencyObject)eventArgs.Instance);
MessageBox.Show(window, message, «Exception» );
eventArgs.FlowBehavior = FlowBehavior.Continue;
>
>
Строго говоря, аспекты в терминологии PostSharp – это, как мы можем видеть, аспект и совет в терминологии АОП.
Для того, что бы указать срез точек пересечения для данного аспекта необходимо в файл настроек сборки (AssemblyInfo.cs) добавить следующую строку:
Или же явно пометить интересующие вас методы атрибутом ExceptionDialog:
[ExceptionDialog]
public BookDTO GetBook(Integer bookId)
Вот собственно и всё: теперь все выброшенные в соответствующих методах исключения будут обрабатываться созданным аспектом.
Продукт платный. Есть Community Edition.
От теории к практике
И так, мы только что увидели, как красиво и эффективно можно решить проблему «выноса за скобки» сквозного функционала в вашем приложении. Однако, это всё теория. На практике всё, естественно, немного иначе 🙂
Прежде всего, в обоих случаях для компиляции и «вшивания» (weaving) аспектов придётся использовать специальный компилятор и тащить вместе с проектом дополнительные библиотеки. Вроде бы, это не проблема: компилятор легко скачивается и интегрируется в среду (например, при использовании maven’a задача сведётся всего лишь к добавлению плагина aspectj-maven-plugin), а множество зависимостей – обычное дело, по крайней мере для Java-приложений (решаемая с помощью того же maven’a). Однако, необходимость включения в проект чего-то, что требует отдельной компиляции, да ещё и не имеет широкого распространения, зачастую отпугивает разработчиков, не смотря на все потенциальные плюсы.
В данном случае решением проблемы может стать Spring Framework [1,2]. Данный фреймворк имеет много достоинств, однако в рамках данной статьи нас интересует его AOP-составляющая. Spring Framework реализует ограниченную AOP-функциональность на чистом Java (C#) без использования сторонних библиотек с помощью создания прокси-объектов (JDK Dynamic Proxy, CGLIB). Другими словами в Spring AOP можно использовать только точки соединения типа «выполнение метода». Однако, как показывает практика, данное ограничение не играет значительной роли, так как для решения большинства задач, требуется точки соединения именно этого типа.
Кроме того, Spring Framework поддерживает конфигурирование приложений c помощью @AspectJ аннотаций, а так же интеграцию аспектов скомпилированных непосредственно с помощью AspectJ.
У себя в компании мы используем именно Spring AOP. Учитывая прочие заслуги Spring Framework, на мой взгляд, он является самой доступной и удобной площадкой для работы с AOP, внося значительный вклад в его популяризацию и развитие.
Резюме
Руководство по Spring. АОП в Spring Framework.
Когда мы рассматривали модули Spring Framework, мы упоминали об Аспекто-ориентированном программировании (далее – АОП). АОП является одним из ключевых компонентов Spring. Смысл АОП заключается в том, что бизнес-логика приложения разбивается не на объекты, а на “отношения” (concerns).
Прим. в русском языке крайне сложно подобрать перевод слова “concern” так, чтобы оно передавало смысл этого слова в контексте АОП.
Функции, которые охватывают несколько точек приложения называются “cross-cutting concerns” или сквозной (комплексной) проблемой и они отделены от самой бизнес-логики приложения.
Ключевой единицей в ООП является “объект”, а ключевой единицей в АОП – “аспект”. В качестве примера “аспекта” можно привести безопасность, кэширование, логирование и т.д. Внедрений зависимостей (DI) позволяет нам отделять объекты приложения друг от друга. АОП, в свою очередь, позволяет нам отделять сквозные проблемы (cross-cuttings) от объектов, к которым они относятся.
Модуль AOP в Spring обеспечивает нас такими сущностями, как “перехватчики” (interceptors) для перехвата приложения в определённые моменты. Например, когда выполняется определённый метод, мы можем добавить какую-то функциональность (к примеру, сделать запись в лог-файл приложения) как до, так и после выполнения метода.
Для понимания АОП, нам прежде всего необходимо ознакомиться с ключевыми понятиями и терминами АОП:
Аспект (Aspect)
Это модуль, который имеет набор программных интерфейсов, обеспечивающих сквозные требования. К примеру, модуль логирования будет вызывать АОП аспект для логирования. В зависимости от требований, приложение может иметь любое количество аспектов.
Объединённая точка (Join point)
Это такая точка в приложении, где мы можем подключить аспект. Другими словами, это место, где начинаются определённые действия модуля АОП в Spring.
Совет (Advice)
Это фактическое действие, которое должно быть предпринято до и/или после выполнения метода. Это конкретный код, который вызывается во время выполнения программы.
Срез точек (Pointcut)
Срезом называется несколько объединённых точек (join points), в котором должен быть выполнен совет.
Введение (Introduction)
Это сущность, которая помогает нам добавлять новые атрибуты и/или методы в уже существующие классы.
Целевой объект (Target object)
Это объект на который направлены один или несколько аспектов.
Плетение (Weaving)
Это процесс связывания аспектов с другими объектами приложения для создания совета. Может быть вызван во время компиляции, загрузки или выполнения приложения.
Существует несколько типов советов (advice):
before
Запускает совет перед выполнением метода.
after
Запускает совет после выполнения метода, независимо от результата его работы (кроме случая остановки работы JVM).
after-returning
Запускает совет после выполнения метода, только в случае его успешного выполнения.
after-throwing
Запускает совет после выполнения метода, только в случае, когда этот метод “бросает” исключение.
around
Запускает совет до и после выполнения метода.
В Spring поддерживаются 2 подхода для реализации АОП:
Применяется конфигурация с помощью конфигурационного XML-файла.
Применяется конфигурация с помощью аннотации
Настоятельно рекомендую ознакомиться с примерами приложений, которые приведены выше по ссылкам. Ссылки на примеры приложений:
Введение в AOP в Spring Boot
В этой статье мы с помощью Spring AOP сделаем три вещи:
Maven-dependency
Чтобы создавать аспекты, в проект Spring Boot необходимо добавить зависимость:
Задача
Итак, допустим, у нас есть некоторый сервис:
При каждом вызове метода someMethod() нам надо измерить время его выполнения. В консоль должна выводиться информация о времени.
И допустим, есть еще метод composeFullName() в компоненте:
Аналогично, при каждом вызове composeFullName() мы должны выводить в консоль имя метода и возвращаемое им значение.
Дополнительные условия
Обратите внимание, что методы someMethod() и composeFullName() находятся в бинах, иначе AOP не сработает. Если у нас не бины, то надо использовать библиотеку AspectJ.
Также мы пометили метод someMethod() обычной Java-аннотацией @LogExecutionTime. Для всех методов, помеченных этой аннотацией, мы и будем логгировать время выполнения. Выглядит аннотация так:
Решить задачу можно и без аннотации, но поскольку аннотация — это удобный способ пометить метод(ы), покажем, как работать в том числе с ней.
С помощью выражений мы задаем те методы, для которых будет выполняться определенное действие (будь то логирование метода или измерение скорости его выполнения). Это действие называется Advice.
Выражение, с помощью которого мы выбираем интересные нам методы, а точнее, сама выборка методов — это Pointcut.
JoinPoint — это точка выполнения программы — а именно, вызов конкретного метода в конкретный момент времени, когда AOP вмешивается и выполняет наш Advice.
Вся вышеперечисленная логика собарается в коде программы в Aspect.
Aspect
Итак, начнем реализовывать логику. Для этого создадим класс LoggingAspect и пометим его аннотацией @Aspect:
Обратите внимание, что класс должен быть Spring-бином.
PointСut
Теперь создадим условие выборки этих наших методов, для которых мы решаем нашу задачу. Тут вариантов задать условие куча, покажем несколько:
Вот так выбираются все методы класса FullNameComposer (в нашем случае там будет только один метод):
Мы создали PointCut с именем stringProcessingMethods(). Это имя мы будем использовать далее в Advice.
На самом деле необязательно выражение для выборки задавать отдельно в PointCut, можно сразу в Advice, ниже мы покажем это.
А пока что еще одно выражение для выборки этого метода:
Здесь звездочка означает выбрать все методы класса, а точки означают, что количество и тип аргументов не важны. Звездочку можно было и вместо возвращаемого типа вставить, это значило бы, что тип не важен.
А вот еще одно выражение, на этот раз, для выборки метода someMethod():
Здесь мы выбираем все методы, помеченные аннотацией @LogExecutionTime.
Advice
Наконец, перейдем к главному — зададим действия, которые выполняются при каждом вызове интересных нам методов.
Есть несколько типов advice:
Первый advice логирует вызовы composeFullName() всегда:
Как видите, в аргументе JoinPoint есть полезная иформация о методе.
Создадим второй advice, который логирует возвращаемое значение в случае нормального завершения метода. У нас он всегда завершается нормально, но тем не менее:
Второй аргумент result и есть возращаемое значение.
Заметьте, что pointcut мы задали прямо в advice. Как было сказано выше, так можно делать.
Наконец, последний advice вычисляет время выполнения метода:
Проверка результата
Давайте сделаем вызовы методов и запустим приложение: