В этой статье я познакомлю читателей с аспектно-ориентированным программирование на примере JSF 2 приложения.
AOP (Аспектно-ориентированное программирование) является одной из самых интересных парадигм абстрактной разработки с тех пор, как была разработана объектно-ориентированная. Как и объектно-ориентированное программирование, AOP позволяет разделять и повторно использовать код, однако расширяя эти возможности во многих областях программы, где это либо сложно, либо не возможно при объектно-ориентированном программировании. Как правило, эти области называют взаимно пересекающимися блоками (cross cutting concerns). Хорошим примером применения взаимно пересекающихся блоков можно назвать периодически повторяющийся код, например, для сбора статистики, трассировки приложения, управления соединениями с базой данных, удаленными соединениями, и др. Используя AOP, эти элементы могут быть выделены из каждого метода и применены через AOP, используя pointcut'ы. Pointcut — это код применимости аспекта, который отвечает на вопросы где (в каких точках соединения (join point)) может быть применена функциональность данного аспекта, которая задаётся с помощью советов (advice). В роли точки соединения может выступать поле класса, конструктор, либо метод. Аспект (aspect) – это набор некоторой сквозной функциональности (периодически повторяющийся код), нацеленный на решение конкретной задачи (сбора статистики, трассировки приложения, управления соединениями с базой данных и т.д.). С помощью совета (advice) можно контролировать изменение полей классов, когда они будут вызываться, вести журнал трассировки вызовов, статистику, выбрасывать исключения, обходить прерванный метод (при возникновении исключительной ситуации) и др. Также стоит заметить, что в контексте ООП сквозной функциональностью можно назвать функциональность, по которой не была проведена декомпозиция.
Для работы аспектов необходимо использовать специальный компилятор и подключить соответствующую библиотеку аспектов в проект.
Далее в статье будет рассматриваться библиотека AspectJ™ 5, при работе с которой в Eclipse лучше использовать этот плагин, а для NetBeans - этот.
Чаще встречаются следующие типы pointcut'ов:
| Тип pointcut'а |
Описание |
| execution(void SomeClass.someMethod(..)) |
Выполнение метода someMethod в классе SomeClass |
| call(void SomeClass.someMethod(..)) |
Вызов метода someMethod в классе SomeClass |
| handler(IOException) |
Pointcut для обработки исключительной ситуации IOException |
| this(SomeType) |
Текущий объект, который принадлежит типу SomeType |
| target(SomeType) |
Объект назначения, который принадлежит типу SomeType |
| within(SomeClass) |
Выполняемый код, который принадлежит классу SomeClass |
| withincode(*SomeClass.someMethod(..)) |
Выполняемый код над методом по стеку вызовов из метода someMethod, который принадлежит классу SomeClass |
| args(*, int) |
любая точка соединения, где есть два аргумента, второй из которых типа int |
| @this(SomeAnnotation) |
Текущий объект, который имеет аннотацию типа SomeAnnotation |
| @target(SomeAnnotation) |
Объект назначения, который имеет аннотацию типа SomeAnnotation |
| @within(SomeAnnotation) |
Выполняемый код над типом, имеет аннотацию типа SomeAnnotation |
| @withincode(SomeAnnotation) |
Выполняемый код над методом либо конструктором, который имеет аннотацию типа SomeAnnotation |
| @args(SomeAnnotation) |
любая точка соединения, где есть один аргумент, который имеет аннотацию типа SomeAnnotation |
Чаще встречаются следующие типы advice'ов:
| Тип advice'а |
Описание |
| @Before("get(someType SomeClass.someField)") |
Выполнение определенного действия до вызова чтения поля someField класса SomeClass |
| @AfterReturning("call(someType SomeClass.someMethod(someType))") |
Выполнение определенного действия после вызова возвращения результата (выполниттся только при нормальном завершении метода (без исключения)) из метода someMethod класса SomeClass |
| @AfterThrowing("call(someType SomeClass.someMethod(someType))") |
Выполнение определенного действия после возникновения исключения при выполнении метода someMethod класса SomeClass |
| @After("call(someType SomeClass.someMethod(someType))") |
Выполнение определенного действия после вызова выполнения метода someMethod класса SomeClass |
| @Around("call(someType SomeClass.someMethod(someType))") |
Выполнение определенного действия вместо вызова выполнения метода someMethod класса SomeClass. В теле для их выполнения можно воспользоваться proceedingJoinPoint.proceed() |
Логические операции pointcut'ов:
| Логическая операция |
Описание |
| && |
Логическое объединение двух pointcut'ов с помощью операции AND |
| || |
Логическое объединение двух pointcut'ов с помощью операции OR |
| ! |
Логическое отрицание pointcut'а |
Групповые символы (wildcards) pointcut'ов:
| Групповой символ |
Описание |
| * |
Удовлетворяет любому числу символов, как части шаблона |
| .. |
При определении пакета удовлетворяет любому количеству дочерних пакетов. Например: com.demo..* – означает, что в любом подпакете пакета com.demo. В определении аргумента удовлетворяет любому количеству аргументов. Например: call(void SomeClass.someMethod(..)) - удовлетворяет вызову из класса SomeClass метода someMethod с любым количеством аргументов, который возвращает тип void |
| + |
При определении класса, добавленный как приставка, удовлетворяет всем подклассам данного класса. Например: handler(Exception+) – означает, что pointcut будет удовлетворять условию обработки исключений для всех подклассов класса Exception |
AOP на примере вызова (call) и выполнения (execution) метода показано ниже в виде рисунка, на котором с помощью толстой линии показан вызов метода, тогда как возвращение результата/исключение - пунктирной линией:
В библиотеки AspectJ™ 5 можно определять pointcut как внутри advice'а (@Before("get(someType SomeClass.someField)")), так и отдельным методом (@Pointcut("execution(void SomeClass.someMethod(..))")).
Графически пример применения AOP на базе библиотеки AspectJ™ 5 можно показать следующим образом:
Из рисунка видно, что у нас определён аспект TraceAspect, у которого есть два pointcut'а: loggableTypePointcut – используется для трассировки в случае, если аннотация @Loggable стоит над классом; loggableMethodPointcut - используется для трассировки в случае, если аннотация @Loggable стоит над методом или конструктором. Также есть два advice метода: public Object log(ProceedingJoinPoint proceedingJoinPoint) throws Throwable – отвечает за обработку действий до и после метода; public void log(Throwable t) – отвечает за обработку исключительной ситуации после её возникновения. Из данного примера advice методы работают исключительно в контексте вызовов методов и/или классов, помеченных аннотацией @Loggable, по соответствующим pointcut'ам.
Рабочий пример проекта с использованием AOP (реализована трассировка вызовов) можно скачать здесь. Для того, чтобы можно было запустить данное приложение, нам необходимо:
Java SDK
Maven
По отношению к предыдущему примеру приложения, который был приложен к прошлой статье, в этом сделаны следующие изменения:
- Добавлена поддержка AOP (при компиляции проекта используется плагин aspectj-maven-plugin)
- Добавлена локализация валидаторов, которые указываются через аннотации над полями POJO классов
- Добавлены юнит-тесты
- Улучшен код
Статью на русском языке по AOP можно посмотреть здесь. Презентацию по AOP можно скачать здесь. Reference Card к библиотеки AspectJ™ 5 можно скачать здесь.
Для тех, кто всё же заинтересовался технологией JSF 2, хотелось бы обратить внимание на одно из лучших обучающих руководств для быстрого и качественного вхождения в понимания и работу технологии. Это руководство можно посмотреть здесь. Много нестандартных ситуаций, с которыми можно столкнуться при работе с JSF 2, хорошо описаны здесь. Другие, не менее достойные, источники приведены в предыдущих моих статьях (первая и вторая).
Благодарю за внимание.
Литература
http://en.wikipedia.org/wiki/Aspect-oriented_programming
http://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html
Теги:
Java
web
jsf
ui