SpringAOP详细配置与使用

2024-05-24 15:32
文章标签 配置 使用 详细 springaop

本文主要是介绍SpringAOP详细配置与使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

SpringAOP简介

AOP概念

Spring AOP简单流程图

Spring AOP之Annotation

前置通知(Before advice)

返回后通知(After reurning advice)

抛出异常后通知(After throwing advice)

后置通知(After (finally) advice)

环绕通知(Around advice)

引入(Introduction)

SpringAOP之XML

AOP日志实现

参考文献


SpringAOP简介

    面向切面编程(Aspect Oriented Programming)提供了另一种角度来思考程序的结构,通过这种方式弥补面向对象编程(Object Oriented Programming)的不足。除了类以外,AOP提供了切面,切面对关注点进行模块化,例如横切多个类型和对象的事务管理(这些关注点术语通常称作横切(crosscutting)关注点)。Spring AOP是Spring的一个重要组件,但是Spring IOC并不依赖于Spring AOP,这意味着你可以自由选择是否使用AOP,AOP提供了强大的中间件解决方案,这使得Spring IOC更加完善。我们可以通过AOP来实现日志监听,事务管理,权限控制等等。

AOP概念

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是Java应用程序中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用通过类(基于模式(XML)的风格)或者在普通类中以@Aspect注解(AspectJ风格)来实现。
  • 连接点(Join point):程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中一个连接点总是代表一个方法的执行。个人理解:AOP拦截到的方法就是一个连接点。通过声明一个org.aspectj.lang.JoinPoint类型参数我们可以在通知(Advice)中获得连接点的信息。这个在稍后会给出案例。
  • 通知(Advice):在切面(Aspect)的某个特定连接点上(Join point)执行的动作。通知的类型包括"around","before","after"等等。通知的类型将在后面进行讨论。许多AOP框架,包括Spring 都是以拦截器作为通知的模型,并维护一个以连接点为中心的拦截器链。总之就是AOP对连接点的处理通过通知来执行。个人理解:Advice指当一个方法被AOP拦截到的时候要执行的代码。
  • 切入点(Pointcut):匹配连接点(Join point)的断言。通知(Advice)跟切入点表达式关联,并在与切入点匹配的任何连接点上面运行。切入点表达式如何跟连接点匹配是AOP的核心,Spring默认使用AspectJ作为切入点语法。个人理解:通过切入点的表达式来确定哪些方法要被AOP拦截,之后这些被拦截的方法会执行相对应的Advice代码。
  • 引入(Introduction):声明额外的方法或字段。Spring AOP允许你向任何被通知(Advice)对象引入一个新的接口(及其实现类)。个人理解:AOP允许在运行时动态的向代理对象实现新的接口来完成一些额外的功能并且不影响现有对象的功能。
  • 目标对象(Target object):被一个或多个切面(Aspect)所通知(Advice)的对象,也称作被通知对象。由于Spring AOP是通过运行时代理实现的,所以这个对象永远是被代理对象。个人理解:所有的对象在AOP中都会生成一个代理类,AOP整个过程都是针对代理类在进行处理。
  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能),在Spring中AOP可以是JDK动态代理或者是CGLIB代理。
  • 织入(Weaving):把切面(aspect)连接到其他的应用程序类型或者对象上,并创建一个被通知对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯AOP框架一样,在运行时完成织入。个人理解:把切面跟对象关联并创建该对象的代理对象的过程。

通知(Advice)的类型:

  • 前置通知(Before advice):在某个连接点(Join point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常)。
  • 返回后通知(After returning advice):在某个连接点(Join point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回。
  • 抛出异常后通知(After throwing advice):在方法抛出异常后执行的通知。
  • 后置通知(After(finally)advice):当某个连接点(Join point)退出的时候执行的通知(不论是正常返回还是发生异常退出)。
  • 环绕通知(Around advice):包围一个连接点(Join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

Spring AOP简单流程图

    上图为我个人对AOP流程的一个理解。我把面向对象的过程从一个HttpRquest到访问数据库DB的整个流程看做是一条直线。AOP定义了一个切面(Aspect),一个切面包含了切入点,通知,引入,这个切面上定义了许多的切入点(Pointcut),一旦访问过程中有对象的方法跟切入点匹配那么就会被AOP拦截。此时该对象就是目标对象(Target Object)而匹配的方法就是连接点(Join Point)。紧接着AOP会用过JDK动态代理或者CGLIB生成一个目标对象的代理对象(AOP proxy),这个过程就是织入(Weaving)。这个时候我们就可以按照我们的需求对连接点进行一些拦截处理。可以看到,我们可以引入(Introduction)一个新的接口,让代理对象来实现这个接口来,以实现额外的方法和字段。也可以在连接点上进行通知(Advice),通知的类型包括了前置通知,返回后通知,抛出异常后通知,后置通知,环绕通知。最后也是最骚的是整个过程不会改变代码原有的逻辑。下面我将通过简单的案例对Spring AOP进行演示,过程会包括XML以及Annotation。实例结构基本跟基于SpringMVC+Spring+Hibernate+Maven+Bootstrap的简单Demo以及SpringMVC整合Mybatis+Maven+Bootstrap的简单Demo一致不一样的地方我会贴出代码。

Spring AOP之Annotation

    首先在bean.xml文件中添加<aop:aspectj-autoproxy />开启Spring对@Aspect的支持。

       <context:component-scan base-package="com.ctc" />//IOC自动扫包<aop:aspectj-autoproxy />//使用AOP注解

    声明一个切面,在类UserAspect上加上@Aspect注解。并定义了两个切入点addLog()以及skipMethod()。Spring主要使用的execetion来匹配连接点。此外还有within,this,target等等,这边不再解释有需要可以参考官方文档。此外Spring文档要求定义切入点(Pointcut)的方法的返回值必须的void类型。但是我自己测试了下其他返回类型,还是可以正常使用。不知道是不是因为测试环境的原因,总之就按照官方的来吧。

@Aspect
public class UserAspect {//匹配所有ServiceImpl包下面的所有类的所有方法@Pointcut("execution(* com.ctc.ServiceImpl.*.*(..))")public void addLog(){}//@Pointcut("execution(public * *(..))")//public void skipMethod(){}}

    接着定义一个Advice类在上面加上@Aspect注解,同时必须在类上添加注解@Component否则Spring就扫描不到这个类。下面将对5种通知类型以及一个引入(Introduction)AgeGroup进行演示。

@Aspect
@Component
public class LogAdvice {/*	@DeclareParents(value="com.ctc.ServiceImpl.*+",defaultImpl=Adult.class)public static AgeGroup ageGroup;*///所有的通知都可以使用这种方式,直接把Pointcut跟Advice连接起来,但是为了更好的理解前文的概念以及图片,这边分开定义。//@Before("execution(* com.ctc.ServiceImpl.*.*(..))");@Before("com.ctc.AspectJ.UserAspect.addLog()")public void before(){System.out.println("LogAdvice before advice ");}/*	@AfterReturning("com.ctc.AspectJ.UserAspect.addLog()")public void AfterReturning(){System.out.println("LogAdvice after returning advice ");}@AfterThrowing("com.ctc.AspectJ.UserAspect.addLog()")public void AfterThrowing(){System.out.println("LogAdvice after throwing advice ");}@After("com.ctc.AspectJ.UserAspect.addLog()")public void After(){System.out.println("LogAdvice after advice ");}//除了可以通过名字来指向对应的切入点表达式,还可以可以使用'&&', '||' 和 '!'来合并。//切入点表达式的 args(user,..) 表示某个与切入表达式匹配的连接点它把User对象作为第一个参数,通过这个语法我们可以在通知中访问到这个User对象。@Around("com.ctc.AspectJ.UserAspect.addLog()&&" +"args(user,..)")public void around(ProceedingJoinPoint  joinPoint,User user) throws Throwable{System.out.println("log begin!");System.out.println("log end");}*/}
	@Testpublic void Aspect(){User user = new User();user.setUserName("test");user.setPassword("123");UserService userService = (UserService) cx.getBean("userServiceImpl");userService.addUser(user);
/*		AgeGroup userAdult = (AgeGroup) cx.getBean("userServiceImpl");userAdult.isAdult();System.out.println(userAdult instanceof UserService);*/}//UserServiceImpl中的方法@Overridepublic void addUser(User user) {System.out.println("add into DB");} 

前置通知(Before advice)

返回后通知(After reurning advice)

    如果方法抛出异常,那么返回后通知就不会执行:

抛出异常后通知(After throwing advice)

   

后置通知(After (finally) advice)

   

把userDaoImpl = null删掉。

 

环绕通知(Around advice)

    环绕通知是一种功能比较强大的通知类型,它可以把第一个参数定义为org.aspectj.lang.Joinpoint类型(环绕通知需要定义为ProcessJoinPoint类型,它是JoinPoint的一个子类)。通过ProcessJoinPoint可以调用process()决定是否让连接点(Join point)继续执行,或者是调用getArgs()返回方法参数,getThis()返回代理对象,getTarget()返回目标对象,getSignature()返回正在被通知的方法的相关信息和toString()打印出正在被通知的方法的有用信息。

下面将从四种情况进行演示:

第一种在方法执行前后添加通知:

第二种方法不执行:

第三种抛出异常停止执行:

第四种返回方法的返回值:

       //因为原有的方法为void就是一个空值,这边改用String方便测试。@Overridepublic String addUser(User user) {userDaoImpl.addUser(user);return "add success!";}

引入(Introduction)

    引入在AspectJ中被称为inter-tye声明,它可以使代理对象实现一个给定的接口用来添加额外的方法或字段。在下面案例中,我们首先引入一个新的接口以及接口的实现类,然后再通过@DeclareParents来定义一个引入,其中value表示要引入的目标对象,defaultImpl表示要实现接口的实现类的Class对象。

public interface AgeGroup {public void isAdult();}public class Adult implements AgeGroup {@Overridepublic void isAdult() {System.out.println("Yes,he is an adult.");}}

SpringAOP之XML

    XML的实例就是把前面用Annotation注解的方式转到配置文件中,案例代码不变并且只给出一个案例,不全部还原。

 //为了方便,只保留了一个before用来演示。public class LogAdvice {public void before(){System.out.println("LogAdvice before advice ");}}

  在选择XML还是Annotation上面,XML的配置是Spring用户最熟悉的,可以很清楚的从配置文件中了解到AOP的应用。但是它的缺点在于XML能够支持的功能会比Annotation的方式差一点,例如在注解上面,我们支持两个切入点表达式进行组合。而XML的方式无法做到。而且通过Annotation的方式如果有需要的话可以很容易的移植到AspectJ上,所以Spring团队更喜欢用Annotation的方式。总之仁者见仁智者见智,看需要吧。

AOP日志实现

  AOP能够实现的事情比较多,此处给出如何通过aop进行日志处理,包括方法调用时长,日志链添加,前置通知等。(注:项目中实现日志的方式有很多种。)

@Aspect
@Component
public class LogAdvice {private final Logger logger = LoggerFactory.getLogger(this.getClass());/*** 横切所有controller下的public方法*/@Pointcut("execution(public * com.ctc.controller.*.*(..))")public void controllerPointCut(){}/*** 横切所有service下的public方法*/@Pointcut("execution(public * com.ctc.service.*.*(..))")public void servicePointCut(){}/*** 环绕通知* @param joinPoint* @return* @throws Throwable*/@Around("controllerPointCut()")public BaseResponse aroundController(ProceedingJoinPoint  joinPoint) throws Throwable{String uuid = UUID.randomUUID().toString().replace("-", "");// 添加日志链MDC.put("mdcId", uuid);// 获取Controller入参Object[] objects = joinPoint.getArgs();String request = "";for (Object o : objects) {if (o instanceof BaseRequest) {request = o.toString();break;}}String methodName = joinPoint.getSignature().getName();logger.info("请求开始: methodName = {}, request = {}", methodName, request);long startTime = System.currentTimeMillis();BaseResponse response = (BaseResponse) joinPoint.proceed();long endTime = System.currentTimeMillis();long executeTime = endTime - startTime;logger.info("请求结束: methodName = {}, result = {}, 执行时间: time = {}ms", methodName, response, executeTime);if (MDC.get("reqId") != null) {// 请求结束后移除日志链MDC.remove("reqId");}return response;}/*** 前置通知业务层*/@Before("servicePointCut()" )public void aroundService(JoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();String[] names = methodSignature.getParameterNames();// 获取Service入参Object[] objects = joinPoint.getArgs();StringBuilder sb = new StringBuilder();for (int i = 0; i < objects.length; i++) {sb.append(names[i] + " = " + objects[i]);if (i != objects.length) {sb.append(", ");}}String methodName = methodSignature.getName();logger.info("执行方法:{}; {}", methodName, sb.toString());}
}
@RestController
@RequestMapping("/test")
public class TestController {@Autowiredprivate TestService testService;@GetMapping("/hello")public BaseResponse getInfo() {return ResponseUtil.getSuccessResponse();}@PostMapping("/post")public BaseResponse getInfo2(@RequestBody ProductInfoRequest request) {testService.test1();testService.test2(1, "adff", request);return ResponseUtil.getSuccessResponse(request);}
}@Service
public class TestServiceImpl implements TestService {@Overridepublic void test1() {test3();}@Overridepublic void test2(int i, String s, BaseRequest request) {}// 该示例中test3不会被横切到,因为其方法修饰符为privateprivate void test3() {}
}

执行结果:

--2019-08-03 14:01:59.631 - INFO 7624 --- [6d4848cd552c46c5bcbd8d083f30c2d4] com.ctc.aspect.LogAdvice    : 请求开始: methodName = getInfo, request = 
--2019-08-03 14:01:59.631 - INFO 7624 --- [6d4848cd552c46c5bcbd8d083f30c2d4] com.ctc.aspect.LogAdvice    : 请求结束: methodName = getInfo, result = BaseResponse(code=0, data=null, msg=success), 执行时间: time = 0ms
--2019-08-03 14:03:00.670 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 请求开始: methodName = getInfo2, request = ProductInfoRequest(id=1, name=haha)
--2019-08-03 14:03:00.671 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 执行方法:test1; 
--2019-08-03 14:03:00.675 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 执行方法:test2; i = 1, s = adff, request = ProductInfoRequest(id=1, name=haha), 
--2019-08-03 14:03:00.675 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 请求结束: methodName = getInfo2, result = BaseResponse(code=0, data=ProductInfoRequest(id=1, name=haha), msg=success), 执行时间: time = 4ms

参考文献

https://docs.spring.io/spring/docs/4.3.25.RELEASE/spring-framework-reference/htmlsingle/

 

这篇关于SpringAOP详细配置与使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/998801

相关文章

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

使用Python合并 Excel单元格指定行列或单元格范围

《使用Python合并Excel单元格指定行列或单元格范围》合并Excel单元格是Excel数据处理和表格设计中的一项常用操作,本文将介绍如何通过Python合并Excel中的指定行列或单... 目录python Excel库安装Python合并Excel 中的指定行Python合并Excel 中的指定列P

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

golang1.23版本之前 Timer Reset方法无法正确使用

《golang1.23版本之前TimerReset方法无法正确使用》在Go1.23之前,使用`time.Reset`函数时需要先调用`Stop`并明确从timer的channel中抽取出东西,以避... 目录golang1.23 之前 Reset ​到底有什么问题golang1.23 之前到底应该如何正确的

Redis多种内存淘汰策略及配置技巧分享

《Redis多种内存淘汰策略及配置技巧分享》本文介绍了Redis内存满时的淘汰机制,包括内存淘汰机制的概念,Redis提供的8种淘汰策略(如noeviction、volatile-lru等)及其适用场... 目录前言一、什么是 Redis 的内存淘汰机制?二、Redis 内存淘汰策略1. pythonnoe

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

Linux alias的三种使用场景方式

《Linuxalias的三种使用场景方式》文章介绍了Linux中`alias`命令的三种使用场景:临时别名、用户级别别名和系统级别别名,临时别名仅在当前终端有效,用户级别别名在当前用户下所有终端有效... 目录linux alias三种使用场景一次性适用于当前用户全局生效,所有用户都可调用删除总结Linux

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni