Java: Аспектно-ориентированное программирование с помощью Spring Framework.
АОП с помощью Spring - пример.
Очень часто в качестве примеров использования АОП приводят логирование. Это действительно наглядный пример, но на первый взгляд может показаться, будто АОП и пригодно разве что для логирования, а для чего же еще применять эту концепцию - не особо понятно. Поняв подход АОП, его можно применять во множестве ситуаций. Просто так взять и придумать такие ситуации бывает поначалу сложно, но зная суть АОП и столкнувшись с ситуацией где другие способы выглядят как-то "некрасиво", на ум приходит мысль, что именно здесь АОП подойдет идеально. Посмотрим на простой, но детальный пример, как использовать АОП в Spring. Предположим у нас класс, который отвечает за обработку счетов. Не важно что конкретно он делает, но у него может быть множество методов, которые работают с одним и тем же объектом - счетом. Перед тем как обработать счет, его нужно проверить по заданному алгоритму. Проще простого - создадим метод для проверки и добавим в каждый из методов нашего сервиса. Вдруг появляется проблема - начиная с завтрашнего утра, счета нужно проверять по другому. Окей, создадим другой метод проверки и изменим каждый из методов нашего класса, сервиса обработки счетов. Подобные изменения возможны и в дальнейшем, а возможно нам придется использовать старый алгоритм проверки, так что приходится менять сервис, пусть и не существенно (добавлять разные методы проверок). Еще проблема - теперь для определенного вида счетов нужно выполнять отдельные действия, уже после валидации данных. Нужен второй этап проверки в каждом из методов сервиса, и дополнительный сценарий обработки. Это конечно утрированный пример, но представьте что методов у сервиса работы со счетами очень много, а постоянно менять метод проверки не представляется хорошим - нужно оставить различные варианты проверок. Значит нам нужно вносить соответствующую функциональность и менять ее - в каждом из методов сервиса. С помощью АОП можно сделать это более красиво и гибко. Целью для создания совета будет сервис обработки счетов. Срезом будет являться вызов каждого из методов этого класса. Перед вызовом любого из его методов мы проверим аргументы, если они прошли проверку - выполним метод. Кроме того, если это необходимо - можно даже пропустить выполнение целевого метода, заменив его совсем другой реализацией. Иными словами - добавим функциональность для каждого из целевых методов, не изменяя эти методы. Здесь небольшое отступление. Итак, мы будем использовать точку соединения (только такие точки соединения и доступны в Spring) - Method Invocation. Совет (логика) будет выполняться вместо целевого метода. Этот тип советов так и называется "вместо". Вот типы советов, которые есть в Spring:
Перед (before) - Это тип совета, когда логика выполняется перед вызовом определенного метода. Совет "перед" имеет полный доступ к аргументам метода, но не имеет контроля за дальнейшим выполнением метода.
После возврата (after returning) - Имеет доступ к аргументам метода и возвращаемому значению, но не имеет контроля над выполнением метода. Выполняется только в случае успешного завершения метода.
После (after) - Аналогично пункту 3, за тем исключением, что этот совет выполняется в любом случае, даже если метод завершился не успешно.
Вместо (around) - Полный доступ к аргументам и возвращаемому значению, а также полный контроль над методом - его можно даже пропустить или заменить другой реализацией. Этот тип и будет использован в примере.
Перехват (throws) - Выполняется только в том случае если метод сгенерировал определенный тип исключения.
Введение (introduction) - Для указания реализации метода, которая должна быть введена советом. Итак, в нашем примере мы создадим совет типа "вместо" для сервиса обработки счетов. В Spring для реализации АОП используется понятие прокси. Это объект который "содержит" цель и который ответственен за применение советов.
В нашем примере применение совета к методу будет выглядеть так:
import java.math.BigDecimal; public class Bill { private BigDecimal summ; private String currency; private String country; //getters and setters }
И сервис работы со счетами, который будет целевым объектом:
public class PaymentService { public String proceedBill1(Bill input) { if (input == null) throw new IllegalArgumentException("bill == null"); //какие-то действия со счетом return "Обработка счета по варианту #1 завершена"; } public String proceedBill2(Bill input) { if (input == null) throw new IllegalArgumentException("bill == null"); //какие-то другие действия со счетом return "Обработка счета по варианту #2 завершена"; } }
Для начала включим необходимые зависимости в проект:
<dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.2.4.RELEASE</version> </dependency>
Для того, чтобы создать совет вместо, нужно реализовать интерфейс MethodInterceptor. Переопределенный метод invoke получает доступ к аргументам метода, который будет вызван на целевом объекте, к его выполнению и к возвращаемому значению. Перед тем как выполнить метод, мы проверяем входные данные (наличие валюты счета и страны). Кроме того, для жителей Кокосовых островов у нас конечно же есть специальный вариант обработки их счетов. Поэтому в этом случае мы заменяем целевой метод своей реализацией:
import java.math.BigDecimal; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class PaymentAdviser implements MethodInterceptor { @Override public Object invoke(MethodInvocation object) throws Throwable { //аргуметы, переданные целевому методу Object[] arguments = object.getArguments(); for (Object buff: arguments) { if (buff instanceof Bill) { Bill bill = (Bill) buff; if (bill.getCurrency() == null || bill.getCurrency().isEmpty() || bill.getSumm() == null || bill.getSumm().equals(BigDecimal.valueOf(0))) { return "Не указаны: сумма и/или валюта счета!"; } //Для этой страны обработка счета идет по //другому, отдельному сценарию if (bill.getCountry() != null) if (bill.getCountry().equals("Кокосовые острова")) return this.proceedBill3(bill); } } //если все проверки пройдены - мы просто //вызываем целевой метод return object.proceed(); } private String proceedBill3 (Bill input) { //какие-то действия, которые не предусмотрены //в сервисе обработки счетов return "Это счастливый счет, для него у нас" + " есть специальный вариант обработки!"; } }
Теперь создадим тест в методе Main:
import java.math.BigDecimal; import org.springframework.aop.framework.ProxyFactory; public class Main { public static void main(String[] args) { //прокси - реализация АОП в Spring ProxyFactory pf = new ProxyFactory(); //объект, для которого будет выполняться совет pf.setTarget(new PaymentService()); //созданный нами совет pf.addAdvice(new PaymentAdviser()); //объект "сервис платежей" с которым будем работать PaymentService service = (PaymentService) pf.getProxy(); //тесты для трех вариантов, предусмотренных в совете Bill test1 = new Bill(); test1.setCountry("Россия"); test1.setCurrency("RUB"); test1.setSumm(new BigDecimal("100.50")); Bill test2 = new Bill(); test2.setCountry("США"); test2.setCurrency("USD"); Bill test3 = new Bill(); test3.setCountry("Кокосовые острова"); test3.setCurrency("USD"); test3.setSumm(new BigDecimal("1000000.00")); //вызываем ЛЮБОЙ из методов на целевом объекте System.out.println("Счет 1: " + service.proceedBill1(test1)); System.out.println("Счет 2: " + service.proceedBill2(test2)); System.out.println("Счет 3: " + service.proceedBill1(test3)); } }
Конечно же для создания прокси в Spring можно использовать не только ProxyFactory, если объекты создаются с помощью внедрения зависимостей - тогда используется ProxyFactoryBean. Использование Spring АОП в сочетании с механизмом внедрения зависимостей позволяет создать очень гибкую, расширяемую архитектуру программы. В примере ProxyFactory используется для того, чтобы не писать лишнего. В результате выполнения нашего теста мы получим такой консольный вывод: Счет 1: Обработка счета по варианту #1 завершена Счет 2: Не указаны: сумма и/или валюта счета! Счет 3: Это счастливый счет, для него у нас есть специальный вариант обработки! Все именно так как и ожидалось!
В книге рассматривается в том числе АОП в Spring Pro Spring 3 - Clarence Ho, Rob Harrop
Теги: programming javaEE java
comments powered by Disqus