что такое aop java
Аспектно-ориентированное программирование в Spring Framework
Поначалу я не буду грузить вас теорией, поэтому давайте пока создадим мавеновский проект и добавим в помник следующие зависимости:
Теперь создадим интерфейс Performer
И заимплементим его в классе PerformerImpl
Далее нам нужно создать класс с советами, они же advices. Назовем его Aspect.
Теперь создадим xml, где будем настраивать наше спринговое приложение.
Итак немного теории.
Как эт о работает, и зачем это нужно?
Нужный функционал (join points) мы объединяем в pointcut, объявляем советы, которые будем использовать для данного pointcut’а, и средствами АОП (создается так называемый перехватчик или Proxy, который берет управление на себя) «внедряем» в код приложения. Такую функциональность еще называют сквозной или cross-cutting.
Подробнее о Proxy: http://en.wikipedia.org/wiki/Proxy_pattern
Если вы подумали, что с помощью АОП, мы можем выполнять дополнительные действия над существующим кодом, при этом не изменяя его, то вы правы!
Способов применению этому много: логирование, безопасность, управление транзакциями и т.д., когда существующий класс или метод должен знать только то, что он выполняет. В нашем случае мы используем логирование. Согласитесь, что нерезонно его «пихать» прямо внутрь метода. Методу неважно, что и как мы логируем, ему главное выполнить свою задачу. Используя АОП, мы разбиваем код на несколько подмодулей, тем самым делая его более читаемым и поддерживаемым.
Вернемся к нашем коду
Итак мы разобрались, как все работает и для чего это нужно. Так давайте запустим наше приложение. Для этого создадим новый класс с методом main.
При запуске мы видим следующее:
Итак, не изменяя интерфейс и класс его реализующий, мы подключили логирование, которое запускается до и после выполнения метода. А теперь давайте проверим работу остальных советов. Для этого создадим новый класс, назовем его Worker:
Теперь изменим наш xml файл: добавим новый бин и аспект с новыми советами.
И теперь метод main:
При запуске, с шансом 50% мы будем видеть либо
либо
Теперь разберемся с последним советом, а именно around, который объединяет несколько советов в один. Для этого добавим новый метод в класс Aspect:
Теперь вернемся к нашему перформеру и изменим наш аспект в xml файле.
Запустим наше приложение уже от не от воркера, а перформера. Думаю, вы знаете что делать 😉
После запуска мы должны увидеть:
Тем самым с помощью around мы объединили все 4 совета в 1.
Заключение
Если Вам понравилась статья, проголосуйте за нее
Голосов: 28 Голосовать
Аспектно-ориентированное программирование, Spring AOP
Аспектно-ориентированное программирование (АОП) — это парадигма программирования являющейся дальнейшим развитием процедурного и объектно-ориентированного программирования (ООП). Идея АОП заключается в выделении так называемой сквозной функциональности. И так все по порядку, здесь я покажу как это сделать в Java — Spring @AspectJ annotation стиле (есть еще schema-based xml стиль, функциональность аналогичная).
Выделении сквозной функциональности
Т.е. есть функциональность которая затрагивает несколько модулей, но она не имеет прямого отношения к бизнес коду, и ее хорошо бы вынести в отдельное место, это и показано на рисунке выше.
Join point
Join point — следующее понятие АОП, это точки наблюдения, присоединения к коду, где планируется введение функциональности.
Pointcut
Pointcut — это срез, запрос точек присоединения, — это может быть одна и более точек. Правила запросов точек очень разнообразные, на рисунке выше, запрос по аннотации на методе и конкретный метод. Правила можно объединять по &&, ||,!
Advice
Advice — набор инструкций выполняемых на точках среза (Pointcut). Инструкции можно выполнять по событию разных типов:
Aspect
Aspect — модуль в котором собраны описания Pointcut и Advice.
Сейчас приведу пример и окончательно все встанет (или почти все) на свои места. Все знаем про логирование кода который пронизывает многие модули, не имея отношения к бизнес коду, но тем не менее без него нельзя. И так отделяю этот функционал от бизнес кода.
Аспект с описанием Pointcut и Advice.
И вызывающий тестовый код
я запросил все public методы MyService с любым типом возврата * и количеством аргументов (..)
В Advice Before и After которые ссылаются на Pointcut (callAtMyServicePublic), я написал инструкции для записи в лог. JoinPoint это не обязательный параметр который, предоставляет дополнительную информацию, но если он используется, то он должен быть первым.
Все разнесено в разные модули! Вызывающий код, целевой, логирование.
Результат в консоли
Правила Pointcut могут быть различные
Несколько примеров Pointcut и Advice:
Запрос по аннотации на методе.
Запрос на конкретный метод с указанием параметров целевого метода
Pointcut для результата возврата
Пример проверки прав на Advice типа Around, через аннотацию
Методы которые необходимо проверять перед вызовом, на право, можно аннотировать «SecurityAnnotation», далее в Aspect получим их срез, и все они будут перехвачены перед вызовом и сделана проверка прав.
Т.е. в вызывающем коде и целевом, проверка прав отсутствует, только непосредственно бизнес код.
Пример профилирование того же сервиса с использованием Advice типа Around
Если запустить вызывающий код с вызовами методов MyService, то получим время вызова каждого метода. Таким образом не меняя вызывающий код и целевой я добавил новые функциональности: логирование, профайлер и безопасность.
Пример использование в UI формах
есть код который по настройке скрывает/показывает поля на форме:
так же можно updateVisibility убрать в Advice типа Around
Руководство по 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-файла.
Применяется конфигурация с помощью аннотации
Настоятельно рекомендую ознакомиться с примерами приложений, которые приведены выше по ссылкам. Ссылки на примеры приложений:
ФИНЭКОСОФТ
Аспектно-Ориентированное Программирование в Spring
Введение
Аспектно-ориентированное программирование, сокращенно АОП (Aspect Oriented Programming или AOP) вместе с принципом конфигурирования зависимостей (dependency injection-DI) и абстракции сервисов (Enterprise Service Abstraction) являются основными принципами, на которых построен главный продукт компании SpringSource – Spring Framework.
В основе аспектно-ориентированного программирования лежит понятие crosscutting concerns, которое не имеет пока устоявшегося эквивалента в русском языке. Наиболее близким по смыслу считается и чаще всего используется словосочетание «сквозная функциональность», которому и будет следовать данная статья в дальнейшем.
В основе аспектно-ориентированного программирования лежит понятие crosscutting concerns, которое не имеет пока устоявшегося эквивалента в русском языке. Наиболее близким по смыслу считается и чаще всего используется словосочетание «сквозная функциональность», которому и будет следовать данная статья в дальнейшем.
Под сквозной функциональностью понимается функциональность, реализовать которую в отдельном компоненте языка программирования традиционными средствами процедурного или объектно-ориентированного программирования или очень сложно, или вообще невозможно, поскольку эта функциональность необходима в большей части модулей системы. Кроме того, эта функциональность не относятся напрямую к предметной области. Примером такой функциональности является протоколирование работы системы (logging). Если надо регистрировать время начала и окончания выполнения методов некоторых классов не используя аспектно-ориентированного программирования, то в исходном коде каждого метода 2 раза явно используется функция записи в журнал. Представив себе прикладную систему даже средней сложности, можно легко понять то огромное количество необходимой работы, которую необходимо сделать, в случае изменения интерфейса протоколирования.
Использование аспектно-ориентированного программирования помогает следовать принципу разделения ответственности (separation of concerns), что положительно сказывается на многих характеристиках разрабатываемой информационной системы, некоторыми из которых являются следующие:
Основные понятия АОП:
Существует так называемый Альянс АОП (AOP Alliance), который объединяет усилия многих коммерческих компаний и проектов (в том числе и SpringeSource) по разработке стандартных интерфейсов для различных реализаций АОП. Альянс является довольно консервативной организацией и в настоящее время определяет весьма ограниченный набор АОП функциональности, часто не являющейся достаточной для разработчиков. Но когда это возможно создатели Spring Framework для обеспечения переносимости предоставляют разработчикам возможность использовать стандартные интерфейсы, а не определяют аналогичные собственные.
Различные типы Аспектно-Ориентированного Программирования
Существует два различных способа реализации аспектно-ориентированного программирования: статический и динамический. Эти способы различаются моментами времени, когда происходит связывание (weaving) и способом, как это связывание происходит.
Статическое АОП
При статической реализации аспектно-ориентированного программирования связывание является отдельным шагом в процессе построения программного продукта (build process) путем модификации байт-кода (bytecode) классов, изменяя и дополняя его необходимым образом.
Полученный в результате такого подхода код является более производительным, чем при использовании динамического АОП, так как во время исполнения (runtime) нет необходимости отслеживать момента, когда надо выполнить ту или иную сквозную функциональность, представленную в виде совета (aspect).
Недостатком такого подхода реализации аспектно-ориентированного программирования является необходимость перекомпилирования приложения даже в том случае, когда надо только добавить новый срез (pointcut).
Динамическое АОП
Продукты, реализующие динамический вариант АОП отличается от статического тем, что процесс связывания (weaving) происходит динамически в момент исполнения. В Spring Framework используется именно такой способ связывания и это реализовано с помощью использования специальных объектов-посредников (proxy) для объектов, к которым должны быть применены советы (advice). Недостатки статического подхода АОП являются достоинствами динамического: поскольку связывание происходит динамически, то нет необходимости перекомпилировать приложение для изменения аспектов. Однако эта гибкость достигается ценой небольшой потери производительности.
Архитектура Spring АОП
Как уже было отмечено, реализация аспектно-ориентированного программирования в Spring основана на использовании объектов-посредников (proxy). Создание посредников возможно программным образом, используя класс ProxyFactory, однако на практике чаще все используется декларативный способ создания посредников, основанный на ProxyFactoryBean.
Суть посредников можно объяснить на следующем примере. Предположим обычный вариант взаимодействия двух объектов, когда объект класса Caller вызывает метод operationA объекта класса Callee:
public class Caller <
private Callee callee ;
public void someMethod () <
Если ссылка callee указывает на объект класса Callee (например получена в результате вызова new Callee() ), то вызов происходит напрямую, так как это продемонстрировано на диаграмме последовательностей (sequence diagram), показанной на рисунке 1.
Рисунок 1: Прямой вызов метода без участия объекта посредника
Иная ситуация в случае использования объекта-посредника. Посредник имеет тот же интерфейс, что и исходный класс, но он не вызывает сразу же метод объекта, реализующий класс Callee, есть возможность совершить дополнительные действия как до момента вызова (BeforeAdvice), так и после него (AfterAdvice), как это показано на диаграмме, изображенной рисунке 2.
Рисунок 2: Вызов с применением объекта посредника
Аспектами в Spring АОП являются объекты классов реализующих интерфейс Advisor, причем в самом фреймворке уже существуют некоторые реализации, которые можно использовать в приложениях, таким образом избавляя разработчиков от необходимости самостоятельно создавать требуемую функциональность.
Для советов (advice) предусмотрен базовый интерфейс org.aopalliance.aop.Advice, однако он является достаточно общим и не всегда удобным для применения. Поэтому при создании классов, реализующих сквозную функциональность, используются другие интерфейсы, определенные в Spring Framework, описанные в следующей таблице:
Название совета
Интерфейс
Этот тип совета предоставляет возможность выполнить дополнительные действия перед вызовом метода, определенного для точки соединения. Класс, реализующий before advice, имеет доступ как к целевому объекту (target), так и к аргументам метода точки соединения, однако с помощью советов данного типа невозможно отказаться от выполнения метода.
AfterReturningAdvice выполняется после завершения метода, определенного для точки соединения. Этот совет доступ к целевому объекту (target), к аргументам метода точки соединения и к возвращаемому методом объекту.
Advice, реализующий определенный Альянсом АОП интерфейс org.aopalliance.intercept.MethodInterceptor, выполняется вместо целевого метода, а сам целевой метод передается аcпекту в качестве параметера, чтобы вызвать его при необходимости в соответствующий момент. Используя Advice возможно вообще проигнорировать вызов целевой функции
ThrowsAdvice перехватывает исключения, сгенерированные внутри метода, для которого определена точка соединения
Специальный тип совета, используя который возможно который добавить новую функциональность к исходному классу.
Использование Spring AOP программным образом
Использование ProxyFactory для создания целевого объекта
Создание объекта-посредника программным образом будет показано на примере создания аспекта, реализующего интерфейс AroundAdvice, который представляет собой реализацию простейшего профайлера, который можно использовать для того, чтобы узнать, сколько времени выполняется код метода. С его помощью в тестовой эксплуатации можно использовать вариант с профайлером, оптимизируя по мере надобности код, а в промышленной эксплуатации без него. При этом основная бизнес функциональность код самого метода остается одинаковой в обоих случаях.
В этом примере целевой класс, который будет дополнен советом (advice), состоит из одного единственного метода., логика которого проста: метод «засыпает» на время от 0 до 10 секунд, этот период каждый раз генерируется случайным образом. Исходный код класса выглядит следующим образом:
public class ContainingLongRunningMethodClass <
public void longLoop() <
int delay = ( int ) (Math.random() * 10);
System.out.println(» Delay time : » + delay);
> catch (InterruptedException e) <
Реализация совета (advice) состоит из выдачи на консоль информации о текущем времени, вызовом целевого метода, для которого определена точка соединения, и снова выдачи текущего времени. Исходный код совета имеет следующий вид:
public class DisplayTimeIntercepter implements MethodInterceptor <
public Object invoke(MethodInvocation method) throws Throwable <
Object value = method.proceed();
return value;
Остается продемонстрировать как создается аспект программным способом с помощью класса ProxyFactory. Сначала (1) инициализируется экземпляр целевого класса ContainingLongRunningMethodClass, длительность выполнения методов которого требуется узнать. Затем создается экземпляр ProxyFactory (2), в который потом передаются уже созданный ранее целевой объект (3) и экземпляр advice (4). Теперь можно получить объект-посредник proxy, который имеет тот же интерфейс, что и исходный целевой объект, однако вызовы методов будут «дополнены» выводом информации о времени начала и окончания их работы (5):
public class AroundAdviceProgrammedExample <
public static void main(String[] args) <
new ContainingLongRunningMethodClass(); (1)
ProxyFactory pf = new ProxyFactory(); (2)
pf.addAdvice( new DisplayTimeIntercepter()); (4)
ContainingLongRunningMethodClass proxy = (5)
В результате выполнения этой программы на консоли появится примерно следующий:
Time before method launch: Mon Oct 19 18:18:36 CEST 2010
Time after method launch: Mon Oct 19 18:18:43 CEST 2010
Использование Pointcut при создании целевого объекта
Создание объекта-посредника программным образом, который был продемонстрирован в предыдущей части, неудобен тем, что в результате сквозная функциональность совета (advice) будет применяться для вызова любого метода целевого объекта. Это не всегда является желательным, необходимо дополнить только какие-то определенные методы исходного объекта, или, выражаясь в терминах АОП, из всех возможных точек соединения (joinpoints) выбрать необходимый срез (pointcut).
Для создания среза в Spring необходимо создать класс, реализующий интерфейс Pointcut, в котором определены два метода:
public interface Pointcut <
Метод getClassFilter возвращает класс, реализующий интерфейс ClassFilter, содержащий единственный метод:
public interface ClassFilter <
boolean matches(Class clazz);
Метод matches возвращает истину (true), если в качестве параметра передан класс, для которого необходимо выполнить дополнительную функциональность и ложь (false) если нет.
Класс, реализующий интерфейс MethodMather, экземпляр которого возвращает метод getMethodMatcher, имеет несколько более сложный вид:
public interface MethodMather <
boolean matches(Method m, Class targetClass);
boolean matches(Method m, Class targetClass, Object[] args);
Spring поддерживает 2 типа MethodMather: статический и динамический. В зависимости от возвращаемого значения функции isRuntime окружение Spring считает, что MethodMather является динамическим (возвращается true), или статическим (false).
В случае статического Pointcut окружение Spring для принятия решения, нужно ли вызывать код совета (advice), использует метод matches(Method m, Class targetClass) при вызове каждого метода целевого объекта в первый раз. После вызова метода matches возвращаемое значение сохраняется во внутренней кэш-памяти и оно используется впоследствии для принятия решения, такой подход позволяет увеличить производительность системы за счет минимизации количества вызовов метода matches.
В случае динамического Pointcut также используется этот метод matches(Method m, Class targetClass) для определения необходимости использования сквозной функциональности. Однако проверка на этом не заканчивается, после нее в Spring вызывает метод matches(Method m, Class targetClass, Object[] args). Таким образом, динамический MethodMather определяет применимость данной точка соединения (Pointcut) при каждом конкретном вызове (проверяя например значения аргументов метода), а не только на основании статической информации о классе и названии метода.
Spring Framework включает несколько абстрактных классов, реализующих интерфейс Pointcut, которые являются достаточными для большинства возможных ситуаций, и разработчики редко вынуждены создавать имплементацию этого интерфейса с нуля. В приведенной ниже таблице указаны некоторые, наиболее часто используемые из этих абстрактных классов (o.s.a.s. сокращение от org.springframework.aop.support):
Класс, реализующий Pointcut
Описание
Используется для определения статических точек соединения, является наиболее часто используемым способом определения среза (Pointcut)
Применяется при создании динамических точек соединения, которые используют информацию об аргументах метода во время выполнения.
Используется, когда необходимо одновременно два или более точек соединения (Pointcut) используя операции union или intersection
Позволяет определять точка соединения (Pointcut) используя регулярные выражения JDK1.4
Использует аннотации языка Java (annotation) при определении точек соединения
Применяется, когда для определения точек соединения используются язык выражений (expression language) языка AspectJ.
Рассмотрим пример с использованием DynamicMethodMatcherPointcut для создания динамической точки соединения для класса, исходный код которого выглядит следующим образом:
public class ToBeDecoratedClass <
public void rundomSleep() <
int delay = ( int ) (Math.random() * 10);
> catch (InterruptedException e) <
public void printInteger( int n) <
System.out.println( «method printInteger, n=» + n);
Класс ToBeDecoratedClass содержит два метода, но мы хотим отслеживать вызов только одного из них, а именно printInteger, и только в случае, если значение аргумента n превышает 10.
В этом случае реализация DynamicMethodMatcherPointcut может иметь следующий вид:
public class DynamicPointcut extends DynamicMethodMatcherPointcut <
public boolean matches(Method method, Class clazz) <
return method.getName().equals( «printInteger» );
public boolean matches(Method method, Class clazz, Object[] args) <
if (args. length == 0)
return false ;
if (obj instanceof Integer) <
return (Integer) obj > 10;
else
return false ;
public ClassFilter getClassFilter() <
return new ClassFilter() <
public boolean matches(Class clazz) <
return clazz == ToBeDecoratedClass. class ;
Первый метод matches(Method method, Class clazz) проверяет, является ли исходный объект экземпляром класса ToBeDecoratedClass и вызываемый метод printInteger. Если проверка прошла успешно, то Spring использует второй метод matches(Method method, Class clazz, Object[] args) для того, чтобы определить тип аргумента функции и его значение. Если аргумент является целым числом и его значение больше 10, то будет вызван соответствующий метод класса, реализующий сквозную функциональность. В данном случае на системную консоль просто будут выведено время запуска и окончания работы метода printInteger.
Ниже приведен исходный код класса, содержащего дополнительную функциональность:
public class LogInterceptor implements MethodInterceptor <
public Object invoke(MethodInvocation invocation) throws Throwable <
Object value = invocation.proceed();
return value;
Для демонстрации возможности создания и использования динамического среза также создан класс NotToBeDecoratedClass, интерфейс которого полностью идентичен интерфейсу ToBeDecoratedClass, но вызов метода printInteger которого мы не хотим отслеживать.
public class DynamicPointcutExample <
public static void main(String[] args) <
ToBeDecoratedClass toBeDecoratedClass = new ToBeDecoratedClass();
new NotToBeDecoratedClass();
Pointcut pointcut = new DynamicPointcut();
Advice advice = new LogInterceptor();
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
ProxyFactory proxyFactory = new ProxyFactory();
proxyDecoratedClass = (ToBeDecoratedClass) proxyFactory.getProxy();
proxyFactory = new ProxyFactory();