Spring04-AOP(Debug查看执行流程)
1 AOP的几个核心技术
AOP-面向切面编程的实现的核心技术:jvm运行期间对字节码进行修改或者动态生成新的字节码文件(asm技术)。
2 AOP的几个核心概念
AOP在运行期间我们要对class文件做修改或者生成新的。AOP就定义了一套规范,包括了切面、切点、连接点、通知、织入等等这些内容。
(1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。
(2)切面(Aspect):被抽取出来的公共模块,可以用来横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。
(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。
(4)通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。
(5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。
(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。
(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
3 通知
3.1 前置通知
前置通知:在方法执行之前执行的通知。前置通知使用 @Before 注解, 并将切入点表达式的值作为注解值.
@Before("declareJoinPointExpression()")
public void beforMethod(JoinPoint joinPoint){
/*获取方法名*/
String methodName = joinPoint.getSignature().getName();
/*获取方法的参数*/
List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("The method "+methodName+" begins with"+args);
}
外部类切入点表达式方法引用如下:
使用类全路径+方法名
@Before("com.web.aop.LogAspects.pointCut()")
public void logEnd(JoinPoint joinPoint){
System.out.println(""+joinPoint.getSignature().getName()+"结束。。。@After");
}
3.2 后置通知
后置通知:在目标方法执行后执行,无论是否抛出异常。后置通知中不能访问目标方法执行后返回的结果。
@After("execution(* com.web.aop.impl.*.*(int , int ))")
/*表示该包下所有返回类型的所有类的所有方法*/
public void afterMethod(JoinPoint joinPoint){
/*获取方法名*/
String methodName = joinPoint.getSignature().getName();
/*获取方法的参数*/
List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("The method "+methodName+" ends with"+args); }
3.3 返回通知
返回通知 :在方法正常执行返回结果后执行,返回通知可以获取目标方法的返回值。
在返回通知中, 只要将 returning 属性添加到 @AfterReturning 注解中, 就可以访问连接点的返回值。 该属性的值即为用来传入返回值的参数名称。
必须在通知方法的签名中添加一个同名参数,在运行时Spring AOP 会通过这个参数传递返回值。
原始的切点表达式需要出现在 pointcut 属性中。
@AfterReturning(pointcut="execution(* com.web.aop.impl.*.*(int , int ))",returning="result")
/*表示该包下所有返回类型的所有类的所有方法*/
public void afterReturning(JoinPoint joinPoint ,Object result){
/*获取方法名*/
String methodName = joinPoint.getSignature().getName();
/*获取方法的参数*/
List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("The method "+methodName+" ends with "+result); }
3.4 异常通知
在方法抛出异常后执行,可以访问到异常对象。且可以指定在出现特定异常对象时,再执行。
只在连接点抛出异常时才执行异常通知
将 throwing 属性添加到 @AfterThrowing 注解中, 也可以访问连接点抛出的异常.
Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.
如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行。
@AfterThrowing(value="execution(* com.web.aop.impl.*.*(int , int ))",throwing="exception")
/*表示该包下所有返回类型的所有类的所有方法*/
public void afterThrowing(JoinPoint joinPoint ,Exception exception){
/*获取方法名*/
String methodName = joinPoint.getSignature().getName();
/*获取方法的参数*/
List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("The method "+methodName+" throws exception :"+exception); }
3.5 环绕通知
环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.动态代理,手动推进目标方法运行(joinPoint.procced())
环绕通知需要携带ProceedingJoinPoint类型的参数;
环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法;
环绕通知必须有返回值,返回值即为目标方法的返回值 !
对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint 。它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点。
在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法。 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行。
注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed() 的返回值, 否则会出现空指针异常。
@Around("execution(* com.web.aop.impl.*.*(int , int ))")
/*表示该包下所有返回类型的所有类的所有方法*/
public Object aroundMethod(ProceedingJoinPoint joinPoint){ System.out.println("This is aroundMethod...."); /*获取方法名*/
String methodName = joinPoint.getSignature().getName();
/*获取方法的参数*/
List<Object> args = Arrays.asList(joinPoint.getArgs()); Object result = null;
try {
//前置通知
System.out.println("The method "+methodName+" begins with :"+args); result = joinPoint.proceed(); //返回通知
System.out.println();
System.out.println("The method "+methodName+" ends with :"+result);
} catch (Throwable e) {
// 异常通知
System.out.println("The method "+methodName+" throws exception :"+e);
throw new RuntimeException(e);
} //后置通知
System.out.println("The method "+methodName+" ends "); return result;
}
3.6 切点的表达式
4 AOP的执行过程
具体顺序
1)从chain取出around通知对象,执行自定义的aorund通知方法前置通知内容(Object proceed = joinPoint.proceed()之前的内容)
2)从chain取出before通知对象,执行自定义的before通知方法
3)从chain取出after通知对象
4)从chain取出afterreturning通知对象
5)从chain取出atferthrowing通知对象
6)执行连接点的逻辑方法
7)执行自定义的afterthrowing的通知方法(没有异常不会执行)
8)执行自定义的afterreturning的通知方法
9)执行自定义的afterre的通知方法
10)继续执行自定义的around通知方法,执行后置通知内容(Object proceed = joinPoint.proceed()之后的内容)
5 测试代码(配合上图看)
5.1 切面
@Component
@Aspect
public class AopLogUtils { @Pointcut("execution(public * com.ruoyi.weixin.user.AopTest.AopTestServiceImpl.test(..)))")
public void myPointCut(){}; @Around(value = "myPointCut()")
public Object doAround(ProceedingJoinPoint joinPoint)
{
int result = 0;
try {
System.out.println("doAround 前面执行");
Object proceed = joinPoint.proceed();
System.out.println("doAround 后面执行");
}catch (Throwable t){
System.out.println("doAround 出错");
}finally {
System.out.println("doAround finally执行");
}
return result; } @Before(value = "myPointCut()")
public void doBefore(JoinPoint joinPoint)
{
System.out.println("doBefore 执行");
} @After(value = "myPointCut()")
public void doAfter(JoinPoint joinPoint)
{
System.out.println("doAfter 执行");
} @AfterReturning(value = "myPointCut()",returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
{
System.out.println("doAfterReturning 执行");
} @AfterThrowing(value = "myPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e)
{
System.out.println("AfterThrowing 执行");
}
}
5.2 业务代码
public interface AopTestService {
void test();
}
@Service("aoptestservice")
public class AopTestServiceImpl implements AopTestService{ @Override
public void test(){
System.out.println("asdf");
} }
@RestController
public class MyAopTest { @Autowired
@Qualifier("aoptestservice")
private AopTestService aoptestservice; @GetMapping("/lj_weixin/aoptest")
public void test(){ aoptestservice.test();
}
5.3 执行调用debug
进入controller,我们看一下aoptestservice,发现它是一个代理对象
看它的属性,里面有一个CALLBACK数组
class CglibAopProxy implements AopProxy, Serializable {
2 public static class SerializableNoOp implements NoOp, Serializable {}
private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable {}
1 private static class StaticUnadvisedExposedInterceptor implements MethodInterceptor, Serializable {}
private static class DynamicUnadvisedInterceptor implements MethodInterceptor, Serializable {}
private static class DynamicUnadvisedExposedInterceptor implements MethodInterceptor, Serializable {}
3 private static class StaticDispatcher implements Dispatcher, Serializable {}
4 private static class AdvisedDispatcher implements Dispatcher, Serializable {}
5 private static class EqualsInterceptor implements MethodInterceptor, Serializable {}
6 private static class HashCodeInterceptor implements MethodInterceptor, Serializable {}
private static class FixedChainStaticTargetInterceptor implements MethodInterceptor, Serializable {}
0 private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {}
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {}
private static class ProxyCallbackFilter implements CallbackFilter {}
}
6 f7进入方法来到CglibAopProxy.java里面的内部类DynamicAdvisedInterceptor的intercept方法
和上面的CALLBACK数组的元素比对,发现它的0号元素就是DynamicAdvisedInterceptor
往下执行,一直到下面代码执行完
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
chain是一个集合,里面存储了通知对象,我们可以看到,一共有6个元素。
但是我们实际上只定义了5个通知,多了一个元素0号元素:ExposeInvocationInterceptor
1-5号元素就是我们定义的通知:它们的顺序是around-before-after-afterreturn-afterthrowing
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
chain放入CglibMethodInvocation对象中,调用proceed()方法
7 进入CglibAopProxy.java.CglibMethodInvocation.java的proceed方法
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
public Object proceed() throws Throwable {
try {
return super.proceed();
}
8 进入super.proceed,也就是ReflectiveMethodInvocation.java的proceed方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
currentInterceptorIndex = -1 ,this.interceptorsAndDynamicMethodMatchers.size() = 6
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
获取到的interceptorOrInterceptionAdvice是ExposeInvocationInterceptor对象,也就是chain里面的0好元素
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
里面传入的this是上面创建的CglibMethodInvocation对象(它里面有chain对象)
9 进入ExposeInvocationInterceptor的invoke方法
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
MethodInvocation oldInvocation = invocation.get();
invocation.set(mi);
try {
return mi.proceed();
}
finally {
invocation.set(oldInvocation);
}
}
把mi对象(也就是上面传入的this对象-CglibAopProxy.java.CglibMethodInvocation对象)赋值给invocation
invocation是当前类ExposeInvocationInterceptor的一个static final静态变量
private static final ThreadLocal<MethodInvocation> invocation =
new NamedThreadLocal<>("Current AOP method invocation");
10 然后调用mi的proceed方法
就是CglibAopProxy.java.CglibMethodInvocation.java的proceed方法
进入proceed方法
public Object proceed() throws Throwable {
try {
return super.proceed();
}
11 调用super.proceed方法,又回到了ReflectiveMethodInvocation的proceed方法
此时currentInterceptorIndex值为0
get(++this.currentInterceptorIndex)取出来的是AspectAroundAdvice 环绕通知
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
12 执行((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);进入AspectJAroundAdvice.java的invoke方法
进入invokeAdviceMethod方法
进入invokeAdviceMethodWithGivenArgs方法
进入this.aspectJAdviceMethod.invoke方法,也就是Method.java的invoke方法
执行ma.invoke(obj, args);
执行,来到环绕通知方法
执行完System.out.println("doAround 前面执行");这个就是around的第一次执行
13 接着执行我们定义的环绕通知方法里面的代码Object proceed = joinPoint.proceed();进入MethodInvocationProceedingJoinPoint.java的proceed方法
14 进入this.methodInvocation.invocableClone().proceed()也就是CglibAopProcy.java.CglibMethodInvocation.java.proceed方法(又回到这个方法)
15 (同11步)进入super.proceed,就是ReflectiveMethodInvocation.java的proceed方法
此时currentInterceptorIndex为1
取出++this.currentInterceptorIndex号元素,为MethodBeforeAdviceInterceptor,也就是before通知
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
16 执行((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);进入MethodBeforeAdviceInterceptor.java的invoke方法
执行完this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
before通知方法已执行
17 (同11步)调用super.proceed方法,又回到了ReflectiveMethodInvocation的proceed方法
此时currentInterceptorIndex为2
取出++this.currentInterceptorIndex号元素,为AspectJAfterAdvice,也就是after通知
18 执行((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);进入AspectJAfterAdvice.java的invoke方法
直接执行return mi.proceed();并没有去调用我们定义的after通知的方法
19 (同11步)调用super.proceed方法,又回到了ReflectiveMethodInvocation的proceed方法
此时currentInterceptorIndex为2
取出++this.currentInterceptorIndex号元素,为AfterReturningAdviceInterceptor,也就是afterReturning通知
20 执行((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);进入AfterReturningAdviceInterceptor.java的invoke方法
先直接执行Object retVal = mi.proceed();等它执行完成,才去执行this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
也就是我们自定义的通知方法
21 (同11步)调用super.proceed方法,又回到了ReflectiveMethodInvocation的proceed方法
此时currentInterceptorIndex为4
取出++this.currentInterceptorIndex号元素,为AspectAfterThrowingAdvice,也就是afterrThrowing通知
22 执行((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);进入AspectJAfterThrowingAdvice.java的invoke方法
直接执行mi.proceed();执行完成后,采取执行invokeAdviceMethod(getJoinPointMatch(), null, ex);也就是我们自定义的通知方法
23 进入super.proceed,也就是ReflectiveMethodInvocation.java的proceed方法
此时currentInterceptorIndex为5,判断为true,执行return invokeJoinpoint();
24 执行return invokeJoinpoint();
进入CglibAopProxy.java.CglibMethodInvocation.java的invokeJoinpoint方法
执行this.methodProxy.invoke(this.target, this.arguments);来到MethodProxy的invoke方法
25 执行 fci.f1.invoke(fci.i1, obj, args);来到我们的连接点方法
26 一步步执行,回到AspectJAfterThrowingAdvice.invoke方法
回到ThrowingAdvice通知的invoke方法,继续执行,由于没有异常,不会执行catch里面的invokeAdviceMethod(getJoinPointMatch(), null, ex);,所以自定义的通知方法不会执行
27 继续执行,回到AfterReturningAdviceInterceptor的invoke方法
回到了AfterReturning通知的invoke方法
执行this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
一步步往里面走,直到执行我们自定义的通知方法
28 继续执行,回到AspectJAfterAdvice的invoke方法
执行invokeAdviceMethod(getJoinPointMatch(), null, null);
一步步往里面走,直到执行我们自定义的通知方法
29 继续走,回到自定义的around通知方法
执行完
到此,五个通知全部执行完成
Spring04-AOP(Debug查看执行流程)的更多相关文章
- debian内核代码执行流程(一)
本文根据debian开机信息来查看内核源代码. 系统使用<debian下配置dynamic printk以及重新编译内核>中内核源码来查看执行流程. 使用dmesg命令,得到下面的开机信息 ...
- Spark修炼之道(进阶篇)——Spark入门到精通:第九节 Spark SQL执行流程解析
1.总体执行流程 使用下列代码对SparkSQL流程进行分析.让大家明确LogicalPlan的几种状态,理解SparkSQL总体执行流程 // sc is an existing SparkCont ...
- 【阅读SpringMVC源码】手把手带你debug验证SpringMVC执行流程
✿ 阅读源码思路: 先跳过非重点,深入每个方法,进入的时候可以把整个可以理一下方法的执行步骤理一下,也可以,理到某一步,继续深入,回来后,接着理清除下面的步骤. ✿ 阅读本文的准备工作,预习一下Spr ...
- Spring Security 案例实现和执行流程剖析
Spring Security Spring Security 是 Spring 社区的一个顶级项目,也是 Spring Boot 官方推荐使用的安全框架.除了常规的认证(Authentication ...
- SpringMVC执行流程及源码分析
SpringMVC流程及源码分析 前言 学了一遍SpringMVC以后,想着做一个总结,复习一下.复习写下面的总结的时候才发现,其实自己学的并不彻底.牢固.也没有学全,视频跟书本是要结合起来一起, ...
- Tomcat笔记:Tomcat的执行流程解析
Bootstrap的启动 Bootstrap的main方法先new了一个自己的对象(Bootstrap),然后用该对象主要执行了四个方法: init(); setAwait(true); load(a ...
- Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),G ...
- debian下使用dynamic printk分析usb转串口驱动执行流程
看了一篇文章<debug by printing>,文中提到了多种通过printk来调试驱动的方法,其中最有用的就是"Dynamic debugging". “Dyna ...
- asyncio源码分析之基本执行流程
基于async关键字的原生协程 # 定义一个简单的原生协程cor async def cor(): print('enter cor') print('exit cor') print(type(co ...
- mybatis源码专题(2)--------一起来看下使用mybatis框架的insert语句的源码执行流程吧
本文是作者原创,版权归作者所有.若要转载,请注明出处.本文以简单的insert语句为例 1.mybatis的底层是jdbc操作,我们先来回顾一下insert语句的执行流程,如下 执行完后,我们看下数据 ...
随机推荐
- .NET复习总纲
以下是自己学习遇到比较好的课程和学习网站,如果大家有更好的课程推荐,可以打在评论区或者私聊我,让我也进行学习和补充进文档 一..NET基础 官方文档:https://learn.microsoft.c ...
- 让 Serverless 更普惠,阿里云函数计算 FC 宣布全面降价,最大幅度达 37.5%
11月5日,2022 杭州 · 云栖大会上,阿里云宣布函数计算 FC 开启全面降价,vCPU 单价降幅** 11%,其他的各个独立计费项最高降幅达 37.5%**. 本次云栖大会上,阿里云智能总裁张建 ...
- oracle第二步创建表空间、用户、授权
Windows+r→键入sqlplus,输入已安装好的oracle数据库超级管理员账号密码登录.显示: 成功. 创建表空间: 创建用户并默认表空间: 授权该创建用户对数据库的操作: 代码: SQL&g ...
- 【实时数仓】Day03-DWM 层业务:各层的设计和常用信息、访客UV计算、跳出明细计算(CEP技术合并数据识别)、订单宽表(双流合并,事实表与维度数据合并)、支付宽表
一.DWS层与DWM层的设计 1.设计思路 分流到了DWD层,并将数据分别出传入指定的topic 规划需要实时计算的指标,形成主题宽表,作为DWS层 2.需求梳理 DWM 层主要服务 DWS,因为部分 ...
- JavaFX入门笔记
JavaFX入门笔记 背景 Java选修课第四次实验 所需工具 IDEA JavaFX插件(需要Maven) JavaFX Scene Builder 参考资料 https://www.yiibai. ...
- 多种方法实现单例模式 pickle模块
目录 单例模式 类方法@classmethod实现 元类实现 模块实现 装饰器实现 双下call.反射实现 pickle序列化模块 单例模式 比如系统调用打印机,不管你要打印几次,都是调用同一个打印机 ...
- 模型驱动设计的构造块(上)——DDD
为了保证软件实践得简洁并且与模型保持一致,不管实际情况如何复杂,必须运用建模和设计的实践. 某些设计决策能够使模型和程序紧密结合在一起,互相促进对方的效用.这种结合要求我们注意每个元素的细节,对细节问 ...
- Logseq001笔记类--视频悬浮插件--Helium
这是我准备新开的学习记录系列之一 今天写一个插件的介绍吧-- Helium -- 视频悬浮插件 youtube/b站/本地视频都可以导入 主要功能就是你在看视频时,要记一些学习笔记,随着不断往下写,视 ...
- XCTF分站赛ACTF——Crypto
impossible RSA: 没啥好说的,跟我之前文章有道题类似,虽然如此还是花费了很长时间,原因令人落泪,把q = inverse(e,p)的数学式写成了eq mod p导致数学式推导及其困难(能 ...
- 01.Java面试都问啥?
大家好,我是王有志.好久不见,不过这次没有休假,而是搞了个"大"工程,花了点时间自学Python,然后写了"玩具爬虫",爬某准网的面经数据,为来年的" ...