横切关注点最突出的例子是伐木 。 日志记录主要用于通过跟踪方法调用和总体执行流程来调试和故障排除问题。 由于日志记录策略必然会影响系统的每个日志记录部分,因此可以将其视为横切关注点。 因此,日志记录会横切所有已记录的类和方法。
请注意,AOP和OOP不是唯一的。 相反,它们是相辅相成的,它们的组合使用可以帮助我们生产健壮且可维护的软件。 使用AOP,我们首先使用OO语言来实现我们的项目,然后通过实现方面来分别处理代码中的横切关注点。
在我们最喜欢的Spring框架的帮助下,AOP的使用和采用得到了提高。 Spring使AOP使用非侵入性方法更易于集成到我们的项目中。 Justin在JavaCodeGeeks的题为 “ 使用Spring AspectJ和Maven进行面向方面的编程 ”的文章中曾谈到过Spring AOP。 但是,我们最新的JCG合作伙伴 SivaLabs的 Siva也写了一篇关于Spring AOP的很好的文章 ,我想与您分享,这里是。
(注意:对原始帖子进行了少量编辑以提高可读性)
在为企业开发软件应用程序时,我们会从需求收集团队或我们的业务分析师那里收到项目的需求。 通常,这些需求是代表业务活动的功能需求。 但是,在开发软件应用程序时,除了功能需求之外,我们还应该考虑其他一些方面,例如性能,事务管理,安全性,日志记录等。这些被称为非功能需求。
让我们考虑一个BookStore应用程序,它提供对书店的网络访问。 用户可以浏览各种类别的书籍,将一些书籍添加到购物车中,最后结帐,付款并获得书籍。 对于此应用程序,我们可能会收到来自业务分析师的要求,如下所示:
- 登录/注册屏幕以进入BookStore。
- 用户应该能够浏览各种类别的书籍
- 用户应该能够按名称,作者姓名,出版商搜索书籍
- 用户应该能够在购物车中添加/删除图书
- 用户应该能够查看其购物车中当前存在哪些物品
- 用户应该能够通过某些支付网关进行结帐并支付相应的金额
- 应该向用户显示一条成功消息,其中包含购买的所有详细信息。
- 应向用户显示失败消息,并说明失败原因。
- 应该授予BookStore管理员/经理访问添加/删除/更新图书详细信息的权限。
以上所有要求都属于“功能要求”类别。 在执行上述操作时,即使未明确提及,我们也应注意以下事项:
- 基于角色的用户界面访问。 在这里,只有管理员/管理员才有权添加/删除/更新书籍详细信息。 [基于角色的授权]
- 采购中的原子性。 假设一个用户登录到BookStore并将5本书添加到他的购物车中,签出并完成了付款。 在后端实施中,我们可能需要在3个表中输入此购买详细信息。 如果将数据插入2个表后系统崩溃,则应回滚整个操作。 [交易管理]。
- 没有人是完美的,没有系统是完美的。 因此,如果出现问题,并且开发团队必须找出问题所在,则日志记录将非常有用。 因此,日志记录的实现方式应使开发人员应该能够弄清楚应用程序的确切故障原因并进行修复。 [记录中]
上面的隐式要求称为非功能性要求。 除上述之外,对于所有面向公众的网站,性能显然应该是至关重要的非功能性要求。
因此,利用上述所有功能需求,我们可以通过将系统分解为各个组件带来构建系统,同时照顾整个组件的非功能需求。
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");Connection conn = null;try{conn = getConnection();//get database connectionconn.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 tableconn.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。
使用这种方法,我们有几个问题:
- 需要更改类以更改功能或非功能需求。 例如:在开发的某个时刻,如果团队决定将方法进入/退出信息与TimeStamp一起记录下来,我们几乎需要更改所有类。
- 事务管理代码在开始时将自动提交设置为false,在执行数据库操作,提交/回滚操作逻辑时,将在所有DAO中重复执行。
跨越模块/组件的这种要求称为“交叉切割问题”。 为了更好地设计系统,我们应该将这些跨领域的关注点与实际的业务逻辑分开,以便日后更容易更改或增强或维护应用程序。
面向方面的编程是一种使跨领域关注点与实际业务逻辑分离的方法。 因此,让我们遵循AOP方法并重新设计上述两类,将交叉关注点分开。
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 tableflag = true;}catch(Exception e){logger.error(e); } return flag;}
}
现在,让我们创建一个LoggingInterceptor来实现应如何进行日志记录,并为OrderService创建一个代理,该Proxy接受来自调用方的调用,使用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 objectlogEntry(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 detailsIOrderService orderService = getOrderServiceProxy();orderService.placeOrder(order);}
}
可以使用几种AOP框架来将实现与交叉关注点分离开。
- SpringAOP
- AspectJ
- JBoss AOP
让我们看看如何使用Spring AOP将日志记录与实际业务逻辑分开。 在此之前,首先我们需要了解以下术语:
- JoinPoint:连接点是应用程序执行中可以插入方面的点。此点可以是调用方法,引发异常或甚至修改字段。
- 切入点:切入点定义与应在其中编织建议的一个或多个连接点相匹配。 通常,您使用显式的类和方法名称或通过定义匹配的类和方法名称模式的正则表达式来指定这些切入点。
- 方面:方面是建议和切入点的合并。
- 建议:方面的工作称为建议。 这是我们应用于现有模型的附加代码。
SpringAOP支持几种类型的建议,即:
- 之前:此建议在方法调用之前编织了方面。
- AfterReturning:此建议在方法调用后编织方面。
- AfterThrowing:当方法抛出Exception时,此建议将编织方面。
- 围绕:此建议在方法调用之前和之后编织方面。
假设我们有以下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类展示了使用Spring AOP应用Logging Advice的各个方面:
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应该包括的内容:
这是一个用于测试功能的独立测试客户端。
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或以上)
- commons-logging.jar
- aopalliance.jar
- Aspectjrt.jar
- Aspectjweaver.jar
- cglib-nodep-2.1_3.jar
我们可以使用注释@ Before,@ AfterReturning,@ Around等来定义建议的类型。我们可以以不同的方式定义切入点。 例如:
@Around(“ execution(* *。*(..))”)表示这是一个“环绕”建议,将应用于所有包和所有方法中的所有类。
假设我们只想对com.myproj.services包中的所有服务应用建议。 然后切入点声明将是:
@Around(“执行(* com.myproj.services。*。*(..))”)
在这种情况下,“(..)”表示带有任何类型的参数。
如果我们想对许多建议应用相同的切入点,我们可以在方法上定义一个切入点,以后可以参考以下内容。
@Pointcut("execution(* *.*(..))")
protected void loggingOperation() {}@Before("loggingOperation()")
public void logJoinPoint(JoinPoint joinPoint){}
如果必须在同一切入点上应用多个建议,则可以使用@Order批注指定要在其上应用建议的订单。 在前面的示例中,将首先应用@Before。 然后,在调用add()方法时将应用@Around。
就是这样。 这是我们JCG合作伙伴之一的Siva提供的非常简单的说明性教程。
快乐的AOP编码。 别忘了分享!
相关文章:
- 使用Spring AspectJ和Maven进行面向方面的编程
- GWT 2 Spring 3 JPA 2 Hibernate 3.5教程– Eclipse和Maven 2展示
- GWT Spring和Hibernate进入数据网格世界
- 带有Spring和Maven教程的JAX–WS
翻译自: https://www.javacodegeeks.com/2011/01/aspect-oriented-programming-spring-aop.html