使用Spring AOP进行面向方面的编程

面向方面的编程(AOP)是指将辅助功能或支持功能与主程序的业务逻辑隔离开来的编程范例。 AOP是用于分离横切关注点的有前途的技术,这在面向对象的编程中通常很难做到。 以此方式增加了应用程序的模块化,并且维护变得非常容易。

横切关注点最突出的例子是伐木 。 日志记录主要用于通过跟踪方法调用和总体执行流程来调试和故障排除问题。 由于日志记录策略必然会影响系统的每个日志记录部分,因此可以将其视为横切关注点。 因此,日志记录会横切所有已记录的类和方法。

请注意,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。

使用这种方法,我们有几个问题:

  1. 需要更改类以更改功能或非功能需求。 例如:在开发的某个时刻,如果团队决定将方法进入/退出信息与TimeStamp一起记录下来,我们几乎需要更改所有类。
  2. 事务管理代码在开始时将自动提交设置为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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374911.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试题24 二叉搜索树的后序遍历序列

题目描述 输入一个整数数组&#xff0c;判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。1 class Solution {2 public:3 bool VerifySquenceOfBST(vector<int> sequence) {4 if (seque…

习题6-5 使用函数验证哥德巴赫猜想 (20 分)

本题要求实现一个判断素数的简单函数&#xff0c;并利用该函数验证哥德巴赫猜想&#xff1a;任何一个不小于6的偶数均可表示为两个奇素数之和。素数就是只能被1和自身整除的正整数。注意&#xff1a;1不是素数&#xff0c;2是素数。 函数接口定义&#xff1a; int prime( int…

Linux学习笔记 (六)用户管理命令

一、用户帐号 1、超级用户&#xff1a;具有操作系统中的最高权限&#xff0c;用来管理和维护操作系统。root用户。 2、普通用户&#xff1a;由root用户来创建&#xff0c;在宿主目录中具有完全权限。 3、程序用户&#xff1a;由应用程序添加&#xff0c;维护某个应用程序运行。…

使用Spring Security保护GWT应用程序

在本教程中&#xff0c;我们将看到如何将GWT与Spring的安全模块&#xff08;即Spring Security&#xff09;集成在一起。 我们将看到如何保护GWT入口点&#xff0c;如何检索用户的凭据以及如何记录各种身份验证事件。 此外&#xff0c;我们将实现自定义身份验证提供程序&#x…

用Fragment制作的Tab页面产生的UI重叠问题

本文出处&#xff1a;http://blog.csdn.net/twilight041132/article/details/43812745 在用Fragment做Tab页面&#xff0c;发现有时候进入应用会同时显示多个Tab内容&#xff0c;UI发生重叠。 当应用被强行关闭后&#xff08;通过手机管家软件手动强关&#xff0c;或系统为节省…

习题6-6 使用函数输出一个整数的逆序数 (20 分)

本题要求实现一个求整数的逆序数的简单函数。 函数接口定义&#xff1a; int reverse( int number );其中函数reverse须返回用户传入的整型number的逆序数。 我的代码&#xff1a; int reverse( int number ) {int n number,res 0,t 0;n (n>0)?n:-n;while(n ! 0){t…

Tomcat 7上具有RESTeasy JAX-RS的RESTful Web服务-Eclipse和Maven项目

开发Web服务的RESTful方法不断受到越来越多的关注&#xff0c;并且似乎正在将SOAP淘汰。 我不会讨论哪种方法更好&#xff0c;但是我相信我们都同意REST更轻量级。 在本教程中&#xff0c;我将向您展示如何使用RESTeasy开发RESTful服务以及如何将它们部署在Tomcat服务器上。 在…

appcmd命令导入导出站点与应用程序池

在IIS7上导出所有应用程序池的方法: %windir%\system32\inetsrv\appcmd list apppool /config /xml > c:\apppools.xml 这个命令会将服务器上全部的应用程序池都导出来,但有些我们是我们不需要的,要将他们删掉.比如: DefaultAppPoolClassic .Net AppPool如果在导入时发现同名…

卸载apache

1、运行services.msc&#xff0c;在服务中停止 apache 服务。2、运行命令行程序&#xff0c;输入 sc delete apache&#xff0c;删除该服务3、删除apache文件夹。转载于:https://www.cnblogs.com/jiangjieqim/p/5357950.html

使用wowza和xuggler将RTMP转换为RTSP

注意&#xff1a;这是我们的“ Xuggler开发教程 ”系列的一部分。 大家好&#xff01; 在过去的三个月中&#xff0c;我们一直在进行电话会议项目。 我们认为&#xff0c;使用诸如Flex之类的技术的基于Web的应用程序将是此类要求苛刻的项目的最佳方法。 随着软件的复杂性和电信…

如何设置网页标题图标

1、先制作一个名为favicon.ico的小图标&#xff0c;并将其放到网站根目录下 2、在html页面<head></head>标签内加入&#xff1a; <link rel"shortcut icon" href"favicon.ico" />OK转载于:https://www.cnblogs.com/moshengr/p/4600281.h…

C语言pow函数的精度问题

&#xff08;1&#xff09;pow函数返回值是double类型 &#xff08;2&#xff09;pow原型 double pow(double x,double y); (3)sqrt原型 double sqrt(double x); 当输出时函数值赋给整型就会出错&#xff1a; int main()d {int N 1;scanf("%d",&N);for(int i …

习题2-1 求整数均值 (10 分)

吐槽一下&#xff0c;因为少打了一个空格&#xff0c;PTA上老是不给我过&#xff0c;还一直报结果错误&#xff0c;误导我好久&#xff0c;即使是吹毛求疵&#xff0c;也应该提示我格式错误吧&#xff01;&#xff01; 原题&#xff1a; 本题要求编写程序&#xff0c;计算4个整…

iframe高度自适应,终于解决了

一直被iframe的高度自适应的问题困扰着&#xff0c;在项目中也是多次遇到。网上也有不少相关的代码&#xff0c;但是总不能满足自己的要求。在头痛了几次之后终于下定决心解决这个问题。 本代码主要解决的问题是&#xff1a;最外层滚动条随着iframe高度动态变化的问题。如果ifr…

在领域驱动的设计,贫乏的领域模型,代码生成,依赖项注入等方面……

埃里克埃文斯&#xff08;Eric Evans&#xff09;已制定了什么是域驱动设计&#xff08;DDD&#xff09;。 Martin Fowler是DDD的大力支持者和拥护者。 这些都是非凡的名字&#xff0c;几乎可以肯定的是&#xff0c;他们正在支持一些有价值的东西。 我不是在这里对此争论。 也许…

Javascript模块化工具require.js教程

转自&#xff1a;http://www.w3cschool.cc/w3cnote/requirejs-tutorial-1.html, http://www.w3cschool.cc/w3cnote/requirejs-tutorial-2.html 随着网站功能逐渐丰富&#xff0c;网页中的js也变得越来越复杂和臃肿&#xff0c;原有通过script标签来导入一个个的js文件这种方式已…

数据值、列类型和数据字段属性

数据值&#xff1a;数值型、字符型、日期型和空值等。数据列类型 2.1 数值类的数据列类型2.2 字符串类数据列类型 2.3 日期和时间型数据数据列类型 另外&#xff0c;也可以使用整形列类型存储UNIX时间戳&#xff0c;代替日期和时间列类型&#xff0c;这是基于PHP的web项目中常…

全文搜索Apache Lucene简介

在本教程中&#xff0c;我想谈谈Apache Lucene 。 Lucene是一个开源项目&#xff0c;提供基于Java的索引和搜索技术。 使用其API&#xff0c;很容易实现全文搜索 。 我将处理Lucene Java版本 &#xff0c;但请记住&#xff0c;还有一个名为Lucene.NET的.NET端口&#xff0c;以及…

函数scanf

本节介绍输入函数 scanf 的用法。scanf 和 printf 一样&#xff0c;非常重要&#xff0c;而且用得非常多&#xff0c;所以一定要掌握。 概述 scanf 的功能用一句话来概括就是“通过键盘给程序中的变量赋值”。该函数的原型为&#xff1a; # include <stdio.h> int scanf(…

C语言中定义变量位置

C标准的问题 C89规定&#xff0c;在任何执行语句之前&#xff0c;在块的开头声明所有局部变量。 即应该如下&#xff1a;定义变量只能在最开始&#xff0c;scanf等执行语句之前 int N 0;double sum 0;scanf("%d",&N);在C99以及C中则没有这个限制&#xff0c;即…