当前位置: 首页 > news >正文

Spring AOP面向切面的底层原理、注解、切入点表达式、连接点获取方法名参数值等

Spring AOP注解原理与实例

参考视频:https://www.bilibili.com/video/BV14WtLeDEit/?p=41&share_source=copy_web&vd_source=053075eb8bd04ebb7bc161d3f4386d4

在这里插入图片描述

AOP注解

  • @Before():不管方法是否执行成功,都会执行
  • @AfterReturning():方法执行成功,返回结果才会执行
  • @AfterThrowing():方法执行失败,抛出异常才会执行
  • @After():不管方法是否执行成功,都会执行

执行顺序

  1. @Before注解:先于方法之前,执行,最先执行。
  2. 被代理类的方法执行
  3. @AfterReturning:方法返回结果之后,执行
  4. @AfterThrowing:方法返回结果之后,执行。与@AfterReturning互斥,同一时间只能执行一个,要么成功,要么时报。
  5. @After注解:方法结果返回之后,执行,最后执行。
通知方法执行顺序
  1. 正常链路:前置通知->目标方法->返回通知->后置通知
    @Before->目标方法->@AfterReturning->@After
  2. 异常链路:前置通知->目标方法->异常通知->后置通知
    @Before->目标方法->@AfterThrowing->@After

切入点表达式

execution(方法全签名)

@Before(“execution(方法全签名)”)

  • 方法的全签名:方法返回值类型+被代理对象的接口类名路径+方法名(参数类型);
  • 示例:public int com.ssg.aop.service.MathCalculator.add(int,int) throws Exception

在这里插入图片描述

完整写法
@Component
@Aspect
public class MathCalculatorAop {@Before("execution(public int com.ssg.aop.service.MathCalculator.add(int,int))")public void startLog(){System.out.println("[AOP 切面]:@Before 方法之前,MathCalculatorAop.startLog()");}
}
简写
  • 只需要保留,方法返回值类型参数类型即可。
  • 方法名称,可以通过*号代替。
  • *号,表示所有方法都会执行。
  • (..),参数类型写两个点,表示多个参数,任意类型。
  • 最简单的写法:* * (..),匹配任意方法,不能这样写!
@Component
@Aspect
public class MathCalculatorAop {@AfterReturning("execution(int add(int,int))")public void returnLog(){System.out.println("[AOP 切面]:@AfterReturning 方法返回值");}@AfterThrowing("execution(int *(int,int))")public void throwLog(){System.out.println("[AOP 切面]:@AfterThrowing 方法异常");}
}

args(方法参数类型):

  • 同时存在两个@Before注解,@Before(“args()”)切入点表达式优先级高于@Before(“execution()”) ,最终@Before("args")先执行
package com.ssg.aop.aspect;import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Aspect
public class MathCalculatorAop {@Before("execution(int add(int,int))")public void startLog(){System.out.println("[AOP 切面]:@Before,前置");}/*** args(方法参数类型)* args(int,int):当参数类型为int,int时,切面方法执行*/@Before("args(int,int)")public void argsLog(){System.out.println("[AOP 切面] [切入点表达式]:args");}
}

结果预览:

[AOP 切面] [切入点表达式]:args
[AOP 切面]@Before,前置
[AOP 切面]@AfterReturning,返回
[AOP 切面]@After,后置

步骤

  1. 导入AOP依赖。
  2. 编写切面Aspect。
  3. 编写通知方法。
  4. 指定切入点表达式。
  5. 测试AOP动态织入。

1. 导入AOP依赖。

  • pom文件导入依赖。
        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

2. 编写Aspect切面类、3.编写通知方法、4.指定切入点表达式

  • 注意:需要导入@Component交由容器进行管理,否则不生效
package com.ssg.aop.aspect;import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Aspect
public class MathCalculatorAop {@Before("execution(public int com.ssg.aop.service.MathCalculator.add(int,int))")public void startLog(){System.out.println("[AOP 切面]:@Before 方法之前,MathCalculatorAop.startLog()");}@AfterReturning("execution(int add(int,int))")public void returnLog(){System.out.println("[AOP 切面]:@AfterReturning 方法返回值");}@AfterThrowing("execution(int *(int,int))")public void throwLog(){System.out.println("[AOP 切面]:@AfterThrowing 方法异常");}@After("execution(int add(int,int))")public void endLog(){System.out.println("[AOP 切面]:@After 方法之后");}}

5.测试AOP动态织入

package com.ssg.aop;import com.ssg.aop.service.MathCalculator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class AopTest {@AutowiredMathCalculator mathCalculator;@Testpublic void test01(){System.out.println("mathCalculator = " + mathCalculator);int add = mathCalculator.add(1, 2);System.out.println("@Test = " + add);}
}
  • 结果:
mathCalculator = com.ssg.aop.service.impl.MathCalculatorImpl@5b000fe6
[AOP 切面]:@Before 方法之前,MathCalculatorAop.startLog()
方法执行结果 = 3
[AOP 切面]:@AfterReturning 方法返回值
[AOP 切面]:@After 方法之后
@Test = 3

JoinPoint连接点

获取方法名称和参数值

@Component
@Aspect
public class MathCalculatorAop {@Before("execution(int add(int,int))")public void startLog3(JoinPoint joinPoint){// 顶级类型// 强制类型转换,默认的得到的对象是最顶级的,我们需要用到它的子类中的部分方法Signature signature = joinPoint.getSignature();// 类型的全签名MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();// 方法名String name = methodSignature.getName();// 目标方法传递的参数值Object[] args = joinPoint.getArgs();System.out.println("[AOP 切面]:@Before3,前置 连接点 ,方法名:" + name + "参数值:" + Arrays.toString(args));}
}

获取返回值

package com.ssg.aop.aspect;import jdk.jshell.MethodSnippet;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
@Aspect
public class MathCalculatorAop {/*** @AfterReturning 返回通知注解,存在一个returning参数,用于接收返回值。* 1. 在切面返回值通知方法中,添加一个参数:Object result*/@AfterReturning(value = "execution(int add(int,int))", returning = "result")public void returnLog2(JoinPoint joinPoint,Object result){MethodSignature signature = (MethodSignature) joinPoint.getSignature();String name = signature.getName();System.out.println("[AOP 切面]:["+name+"] @AfterReturning,返回值" + result);}
}

获取异常信息

package com.ssg.aop.aspect;import jdk.jshell.MethodSnippet;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
@Aspect
public class MathCalculatorAop {/*** @AfterThrowing 异常通知注解,存在一个throwing参数,用于接受返回的异常信息。* 1. 在切面异常通知方法中,添加一个参数:Exception e* 2. throwing = "e",获取目标方法抛出异常*/@AfterThrowing(value = "execution(int *(int,int))", throwing = "e")public void throwLog2(JoinPoint joinPoint, Exception e) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();String name = signature.getName();System.out.println("[AOP 切面]:[" + name + "] @AfterThrowing,异常信息:" + e.getMessage());}

@Pointcut 简化切入点表达式

  • @Pointcut用于简化切入点表达式
  • 其放置在方法上面,方法名称作为其他AOP注解的引用。
  • 针对需要返回值的场景,需要在AOP注解中,使用参数pointcut=""来引用简化的切入点表达式。

常规写法,不要求返回值参数等

package com.ssg.aop.aspect;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
@Aspect
public class MathCalculatorAopPointcut {@Pointcut("execution(public int com.ssg.aop.service.impl.SummerServiceImpl.add(int,int))")public void pointCutExecution(){}@Pointcut("args(int,int)")public void pointCutArgs(){}/*** args(方法参数类型)* args(int,int):当参数类型为int,int时,切面方法执行* 同一时间只能存在一个@Before注解,后者覆盖前者。*/@Before("pointCutArgs()")public void argsLog() {System.out.println("[AOP 切面] [切入点表达式]:args");}@Before("pointCutExecution()")public void startLog(JoinPoint joinPoint) {// 顶级类型// 强制类型转换,默认的得到的对象是最顶级的,我们需要用到它的子类中的部分方法Signature signature = joinPoint.getSignature();// 类型的全签名MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();// 方法名String name = methodSignature.getName();// 目标方法传递的参数值Object[] args = joinPoint.getArgs();System.out.println("[AOP 切面]:@Before3,前置 连接点 ,方法名:" + name + "参数值:" + Arrays.toString(args));}
}

需要返回值参数、异常信息等

  • @AfterReturning(pointcut = "", returning = "")

@AfterReturning(pointcut = "pointCutExecution()", returning = "result")

package com.ssg.aop.aspect;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
@Aspect
public class MathCalculatorAopPointcut {@Pointcut("execution(public int com.ssg.aop.service.impl.SummerServiceImpl.add(int,int))")public void pointCutExecution(){}@Pointcut("args(int,int)")public void pointCutArgs(){}/*** @AfterReturning 返回通知注解,存在一个returning参数,用于接收返回值。* 1. 在切面返回值通知方法中,添加一个参数:Object result* 2. returning = "result",获取目标方法返回值*/@AfterReturning(pointcut = "pointCutExecution()", returning = "result")public void returnLog(JoinPoint joinPoint, Object result) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();String name = signature.getName();System.out.println("[AOP 切面]:[" + name + "] @AfterReturning,返回值" + result);}/*** @AfterThrowing 异常通知注解,存在一个throwing参数,用于接受返回的异常信息。* 1. 在切面异常通知方法中,添加一个参数:Exception e* 2. throwing = "e",获取目标方法抛出异常*/@AfterThrowing(pointcut = "pointCutExecution()", throwing = "e")public void throwLog(JoinPoint joinPoint, Exception e) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();String name = signature.getName();System.out.println("[AOP 切面]:[" + name + "] @AfterThrowing,异常信息:" + e.getMessage());}@After("pointCutExecution()")public void endLog() {System.out.println("[AOP 切面]:@After,后置");}
}

多切面执行顺序

  • 存在一种情况,对同一个目标类,有多个AOP切面类,如何确定他们的执行顺序呢?
  • 默认情况下,根据切面类的字母顺序A-Z执行。
  • 或者使用@Order(0)注解,数字越小的越先执行。
  • 先执行的切面代理对象会将后执行的切面代理对象包裹。先切面对象执行完前置后,等后切面对象执行完全部通知方法后,再执行后置通知。
    在这里插入图片描述
@Component
@Aspect
@Order(1)
public class MathCalculatorAop {
}

环绕通知

@Around 是 Spring-AOP(或 AspectJ)里功能最强大的通知类型,它把“目标方法”整个包裹起来,让你在方法执行前、执行后、甚至代替原方法去做任何事情。

  • 环绕通知@Around,相当于将@Before前置通知、@AfterReturning返回通知、@AfterThrowing异常通知、@After后置通知,集成到了一起。

  • 一个更比4个强。

  • 注意:在写环绕同通知的异常通知时,因为我们捕获了通知,最后都需要再重新抛出异常

  • 防止存在多个切面类时,环绕通知不抛出异常,他的外层切面类对象,会将其当作返回通知进行处理,导致存在异常情况。实际异常但却返回。

  • 环绕通知需要手动抛出异常。

环绕通知固定写法

  • 方法执行: joinPoint.proceed(args);,可以不加参数。
  • try-cache:必须手动抛出异常。切记切记。
package com.ssg.aop.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;import java.util.Arrays;@Component
@Aspect
public class MathCalculatorAopAround {@Pointcut("execution(int com.ssg.aop.service.*.add(int,int))")public void pointCut() {}/*** 环绕通知* @param joinPoint* @return* @throws Throwable 环绕通知需要手动抛出异常*/@Around("pointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 获取参数信息Object[] args = joinPoint.getArgs();System.out.println("环绕通知 - 前置通知 - 参数信息:" + Arrays.toString(args));Object proceed = null;try {proceed = joinPoint.proceed(args);System.out.println("环绕通知 - 返回通知 - 返回信息:" + proceed);} catch (Exception e) {System.out.println("环绕通知 - 异常通知 - 异常信息" + e.getMessage());// 注意:在写环绕同通知的异常通知时,因为我们捕获了通知,最后都需要再重新抛出异常// 防止存在多个切面类时,环绕通知不抛出异常,他的外层切面类对象,会将其当作返回通知进行处理,导致存在异常情况。实际异常但却返回。// 环绕通知需要手动抛出异常。throw e;} finally {System.out.println("环绕通知 - 后置通知 - ");}return proceed;}}

图解:

多个切面类为什么一定要求环绕切面类要重新抛出异常?
在这里插入图片描述

  1. 当我们的环绕切面类,执行方法抛出异常时。
  2. 如果不抛出异常,则其外层切面类,会将其当作返回通知进行处理。
  3. 也就导致实际异常,结果做了返回处理。
    在这里插入图片描述在这里插入图片描述

底层原理

  • 增强器链:切面中的所有通知方法其实是增强器,他们被组织成一个链路放到集合中,目标方法真正执行前后,会去增强器链中执行哪些需要提前执行的方法。

AOP的底层原理是什么?

  1. Spring会为每个被切面切入的组件创建代理对象(Spring CGLIB 创建的代理对象,无视接口,没有接口也能创建代理对象)。
  2. 代理对象中保存了,切面类里面所有通知方法构成的增强器链。
  3. 目标方法执行时,会先执行增强器链中拿到需要提前执行的通知方法去执行。

代理对象信息

不使用切面的情况下
  • 由容器直接注入mathCalculator目标对象,结果输出为目标对象的信息。
    在这里插入图片描述
使用切面的情况下
  1. 由容器直接注入mathCalculator目标对象。
  2. 由于使用了切面类,对目标方法进行了代理。
  3. 结果输出为代理对象的信息。
  4. 代理对象执行了增强器链相关操作。
    在这里插入图片描述
http://www.dtcms.com/a/348554.html

相关文章:

  • C++STL底层原理:探秘标准模板库的内部机制
  • 从全栈开发到微服务架构:一次真实的Java面试实录
  • 【机器学习】9 Generalized linear models and the exponential family
  • 大模型面试题剖析:微调与 RAG 技术的选用逻辑
  • 【Docker项目实战】使用Docker部署Hibiscus.txt简单日记工具
  • VITE BALABALA require balabla not supported
  • Linux:shell命令
  • 【数据结构】-4-顺序表(上)
  • AI Agent与生成式AI双驱动:AI如何重塑商业格局并创造千亿级增量价值
  • 一套完整的Linux下usb设备驱动包括字符设备驱动吗
  • Docker 安装LDAP(企业级统一账号配置系统)
  • 税务岗位能力提升培训课程推荐:专业成长与证书指南
  • 【Game】Powerful——Punch and Kick(12.3)All Star
  • KingBase数据库迁移利器:KDTS工具深度解析与实战指南
  • Https之(三)TLS双向认证
  • Linux->多线程2
  • openGauss之 无用join消除
  • 如何在 IDEA 中在启动 Spring Boot 项目时加参数
  • Ubuntu 服务器无法 ping 通网站域名的问题解决备忘 ——通常与网络配置有关(DNS解析)
  • 国内使用SSH稳定使用github
  • ROS 与 Ubuntu 版本对应关系
  • 基于Transformer的知识图谱推理模型(KnowFormer)
  • 使用python进行接口测试
  • .net9 解析 jwt 详解
  • Indy HTTP Server 使用 OpenSSL 3.0
  • 采摘机器人设计cad+三维图+设计说明书
  • 学习记录(二十一)-Overleaf中图片文字间隔太大怎么办
  • 【QT入门到晋级】进程间通信(IPC)-共享内存
  • Java数据结构——7.二叉树(总览)
  • 机器学习周报十