Статьи

Аспектно-ориентированное программирование с использованием SpringAOP

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

Давайте рассмотрим приложение BookStore, которое предоставляет веб-доступ к магазину. Пользователь может просматривать различные категории книг, добавлять книги в корзину и, наконец, оформить заказ, произвести оплату и получить книги.

Для этого приложения мы можем получить требования от бизнес-аналитика следующим образом:
1. Экран входа / регистрации, чтобы войти в BookStore.
2. Пользователи должны иметь возможность просматривать различные категории книг.
3. Пользователи должны иметь возможность искать книги по имени, имени автора, издателю.
4. Пользователи должны иметь возможность добавлять / удалять книги в / из его корзины
. может видеть, какие все товары в настоящее время находятся в его корзине
6. Пользователи должны иметь возможность оформить заказ и предоставить возможность оплатить сумму через некоторый платежный шлюз
7. Пользователям будет показано успешное сообщение со всеми деталями его покупок.
8. Сообщение об ошибке будет показано пользователям с причиной сбоя.
9. Администратор / менеджер BookStore должен иметь возможность предоставлять доступ для добавления / удаления / обновления информации о книге.

Все вышеперечисленные требования подпадают под функциональные требования. При реализации вышеизложенного мы также должны позаботиться о следующих вещах, даже если они явно не упомянуты:

  1. Ролевый доступ к пользовательскому интерфейсу. Здесь только администраторы / менеджеры могут получить доступ для добавления / удаления / обновления информации о книге. [Авторизация на основе ролей]
  2. Атомность в закупках. Предположим, что пользователь вошел в Книжный магазин и добавил 5 книг в свою корзину, зарегистрировался и произвел оплату. В серверной реализации нам может понадобиться ввести данные покупки в 3 таблицы. Если после вставки данных в 2 таблицы и сбоя системы вся операция должна быть отменена. [Управление транзакциями].
  3. Никто не совершенен, и ни одна система не является безупречной. Так что, если что-то пошло не так, и команда разработчиков должна выяснить, что пошло не так, регистрация будет наиболее полезной. Поэтому ведение журнала должно быть реализовано таким образом, чтобы разработчик мог выяснить, где именно произошла ошибка, и исправить это.

Вышеуказанные неявные требования называются нефункциональными требованиями. В дополнение к вышеупомянутой производительности, очевидно, будет решающим нефункциональным требованием для многих публичных веб-сайтов.

Таким образом, с учетом всех вышеперечисленных функциональных требований мы можем построить систему, разбивающую всю систему на различные компоненты, заботясь о нефункциональных требованиях во всех компонентах.

public class OrderService
{
private OrderDAO orderDAO;

public boolean placeOrder(Order order)
{
boolean flag =false;
logger.info("Entered into OrderService.placeOrder(order) method");
try
{
flag = orderDAO.saveOrder(order);
}
catch(Exception e)
{
logger.error("Error occured in OrderService.placeOrder(order) method");
}
logger.info("Exiting from OrderService.placeOrder(order) method");
return flag;
}
}

public class OrderDAO
{
public boolean saveOrder(Order order)
{
boolean flag =false;
logger.info("Entered into OrderDAO.saveOrder(order) method");
Connectoin conn = null;
try
{
conn = getConnection();//get database connection
conn.setAutoCommit(false);
// insert data into orders_master table which generates an order_id
// insert order details into order_details table with the generated order_id
// insert shipment details into order_shipment table
conn.commit();
conn.setAutoCommit(true);
flag = true;
}
catch(Exception e)
{
logger.error("Error occured in OrderDAO.saveOrder(order) method");
conn.rollback();
}
logger.info("Exiting from OrderDAO.saveOrder(order) method");
return flag;
}
}

Здесь, в приведенном выше коде, реализация функциональных требований и реализация нефункциональных требований смешиваются в одном месте.
Регистрация ведется по классам OrderService и OrderDAO. Управление транзакциями охватывает DAO.
При этом у нас будет несколько вопросов:

  1. Классы должны быть изменены, чтобы изменить функциональные или нефункциональные требования.
    Например, на более позднем этапе разработки, если команда решает зарегистрировать информацию о входе / выходе метода вместе с меткой времени, нам необходимо изменить почти все классы.
  2. Код управления транзакциями, устанавливающий автоматическую фиксацию на false в начале, выполняющий операции с БД, фиксирующий / откатывающий логику операции, будет продублирован во всех DAO.

Здесь, если мы увидим, что метод входа / выхода ведется во всех модулях. Управление транзакциями распространяется на все DAO.

Такие требования, которые распространяются на модули / компоненты, называются сквозными.

Чтобы лучше спроектировать систему, мы должны отделить эти сквозные проблемы от реальной бизнес-логики, чтобы было легче изменять, улучшать или поддерживать более поздний момент времени.

Аспектно-ориентированное программирование — это методология, в которой отделены сквозные задачи от реальной бизнес-логики.

Итак, давайте следовать методологии АОП и перепроектировать два вышеупомянутых класса, разделяющих сквозные проблемы.

public interface IOrderService
{
public boolean placeOrder(Order order);
}

public class OrderService implements IOrderService
{
private OrderDAO orderDAO;

public boolean placeOrder(Order order)
{
return orderDAO.saveOrder(order);
}
}


public class OrderDAO
{
public boolean saveOrder(Order order)
{
boolean flag =false;

Connectoin conn = null;
try
{
conn = getConnection();//get database connection
// insert data into orders_master table which generates an order_id
// insert order details into order_details table with the generated order_id
// insert shipment details into order_shipment table
flag = true;
}
catch(Exception e)
{
logger.error(e);
}
return flag;
}
}

Теперь давайте создадим LoggingInterceptor, реализующий порядок ведения журнала, и создадим Proxy для OrderService, который принимает вызов от вызывающей стороны, регистрирует записи входа / выхода с помощью LoggingInterceptor и делегирует действительный OrderService.

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

public class LoggingInterceptor
{
public void logEntry(Method m)
{
logger.info("Entered into "+m.getName()+" method");
}
public void logExit(Method m)
{
logger.info("Exiting from "+m.getName()+" method");
}
}

public class OrderServiceProxy implements IOrderService extends LoggingInterceptor
{
private OrderService orderService;

public boolean placeOrder(Order order)
{
boolean flag =false;
Method m = getThisMethod();//get OrderService.placeOrder() Method object
logEntry(m);
flag = orderService.placeOrder(order);
logExit(m);
return flag;
}
}

Теперь вызывающая программа OrderService (OrderController) может получить OrderServiceProxy и разместить заказ как:

public class OrderController
{
public void checkout()
{
Order order = new Order();
//set the order details
IOrderService orderService = getOrderServiceProxy();
orderService.placeOrder(order);
}
}

У нас есть несколько платформ AOP для разделения реализации сквозных задач.

a) Spring AOP

b) AspectJ

b) JBoss AOP

Теперь давайте посмотрим, как мы можем отделить Logging от реальной бизнес-логики с помощью Spring AOP.

Прежде чем

приступить к использованию Spring AOP, сначала нам нужно понять следующее:
JoinPoint: точка соединения — это точка выполнения приложения, в которую может быть включен аспект. Эта точка может быть вызываемым методом, вызывая исключение, или даже поле, которое изменяется

Pointcut:Определение точки среза соответствует одной или нескольким точкам соединения, в которых должен быть создан совет. Часто вы указываете эти pointcut с использованием явных имен классов и методов или с помощью регулярных выражений, которые определяют соответствующие шаблоны имен классов и методов.

Аспект: аспектом является объединение советов и идей.

Совет: Работа аспекта называется советом.

SpringAOP поддерживает несколько типов советов:

    1. Before: этот совет переплетает аспект перед вызовом метода.

    2. AfterReturning: этот совет переплетает аспект после вызова метода.

    3. AfterThrowing: этот совет создает аспект, когда метод генерирует исключение.

    4. Вокруг: этот совет сплетает аспект до и после вызова метода.

 

Предположим, у нас есть следующие интерфейс ArithmeticCalculator и классы реализации.

package com.springapp.aop;
public interface ArithmeticCalculator
{
public double add(double a, double b);
public double sub(double a, double b);
public double mul(double a, double b);
public double div(double a, double b);
}

package com.springapp.aop;
import org.springframework.stereotype.Component;

@Component("arithmeticCalculator")
public class ArithmeticCalculatorImpl implements ArithmeticCalculator
{
public double add(double a, double b)
{
double result = a + b;
System.out.println(a + " + " + b + " = " + result);
return result;
}

public double sub(double a, double b)
{
double result = a - b;
System.out.println(a + " - " + b + " = " + result);
return result;
}

public double mul(double a, double b)
{
double result = a * b;
System.out.println(a + " * " + b + " = " + result);
return result;
}

public double div(double a, double b)
{
if(b == 0)
{
throw new IllegalArgumentException("b value must not be zero.");
}
double result = a / b;
System.out.println(a + " / " + b + " = " + result);
return result;
}
}

Следующий класс LoggingAspect показывает различные фрагменты применения Рекомендации по ведению журнала с использованием SpringAOP.

package com.springapp.aop;

import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect
{
private Log log = LogFactory.getLog(this.getClass());

@Pointcut("execution(* *.*(..))")
protected void loggingOperation() {}

@Before("loggingOperation()")
@Order(1)
public void logJoinPoint(JoinPoint joinPoint)
{
log.info("Join point kind : " + joinPoint.getKind());
log.info("Signature declaring type : "+ joinPoint.getSignature().getDeclaringTypeName());
log.info("Signature name : " + joinPoint.getSignature().getName());
log.info("Arguments : " + Arrays.toString(joinPoint.getArgs()));
log.info("Target class : "+ joinPoint.getTarget().getClass().getName());
log.info("This class : " + joinPoint.getThis().getClass().getName());
}

@AfterReturning(pointcut="loggingOperation()", returning = "result")
@Order(2)
public void logAfter(JoinPoint joinPoint, Object result)
{
log.info("Exiting from Method :"+joinPoint.getSignature().getName());
log.info("Return value :"+result);
}

@AfterThrowing(pointcut="execution(* *.*(..))", throwing = "e")
@Order(3)
public void logAfterThrowing(JoinPoint joinPoint, Throwable e)
{
log.error("An exception has been thrown in "+ joinPoint.getSignature().getName() + "()");
log.error("Cause :"+e.getCause());
}

@Around("execution(* *.*(..))")
@Order(4)
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable
{
log.info("The method " + joinPoint.getSignature().getName()+ "() begins with " + Arrays.toString(joinPoint.getArgs()));
try
{
Object result = joinPoint.proceed();
log.info("The method " + joinPoint.getSignature().getName()+ "() ends with " + result);
return result;
} catch (IllegalArgumentException e)
{
log.error("Illegal argument "+ Arrays.toString(joinPoint.getArgs()) + " in "+ joinPoint.getSignature().getName() + "()");
throw e;
}
}

}

applicationContext.xml

 <beans>

<context:annotation-config/>
<context:component-scan base-package="com.springapp"></context:component-scan>
<aop:aspectj-autoproxy/>

</beans>

И отдельный тестовый клиент для тестирования функциональности.

package com.springapp.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringAOPClient
{

public static void main(String[] args)
{
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator calculator = (ArithmeticCalculator) context.getBean("arithmeticCalculator");
double sum = calculator.add(12, 23);
System.out.println(sum);
double div = calculator.div(1, 10);
System.out.println(div);
}

}

Требуемые банки:

  • Spring.jar (2.5.6 или выше)
  • Обще-logging.jar
  • aopalliance.jar
  • aspectjrt.jar
  • aspectjweaver.jar
  • CGLIB-nodep-2.1_3.jar

Мы можем определить тип совета, используя @Before, @AfterReturning, @Around и т. Д. Мы можем определять pointcuts по-разному.
@Around («execute (* *. * (..))») означает, что это совет Around, который будет применяться ко всем классам во всех пакетах и ​​ко всем методам.
Предположим, если мы хотим применить его только для всех сервисов, находящихся в пакете com.myproj.services, pointcut будет

@Around("execution(* com.myproj.services.*.*(..))").

«(..)» означает с любым типом аргументов.

Если мы хотим применить одни и те же pointcut для многих советов, мы можем определить pointcut для метода и можем сослаться на это позже, как показано ниже.

 @Pointcut("execution(* *.*(..))")
protected void loggingOperation() {}

@Before("loggingOperation()")
public void logJoinPoint(JoinPoint joinPoint)
{
}

Если несколько советов должны быть применены к одной и той же точке, мы можем указать порядок, используя @Order, к которому будут применены подсказки. В приведенном выше примере сначала будет применен @Before, затем будет применен @Around при вызове метода add ().

При использовании AOP код будет более понятным и понятным. SpringAOP является одним из способов реализации AOP и поддерживает только точки соединения вызова метода. AspectJ является еще более мощным и может применяться на нескольких дополнительных точках соединения. Spring также поддерживает интеграцию AspectJ.

От: http://sivalabs.blogspot.com/2011/01/aspect-oriented-programming-using.html