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查看执行流程)的更多相关文章

  1. debian内核代码执行流程(一)

    本文根据debian开机信息来查看内核源代码. 系统使用<debian下配置dynamic printk以及重新编译内核>中内核源码来查看执行流程. 使用dmesg命令,得到下面的开机信息 ...

  2. Spark修炼之道(进阶篇)——Spark入门到精通:第九节 Spark SQL执行流程解析

    1.总体执行流程 使用下列代码对SparkSQL流程进行分析.让大家明确LogicalPlan的几种状态,理解SparkSQL总体执行流程 // sc is an existing SparkCont ...

  3. 【阅读SpringMVC源码】手把手带你debug验证SpringMVC执行流程

    ✿ 阅读源码思路: 先跳过非重点,深入每个方法,进入的时候可以把整个可以理一下方法的执行步骤理一下,也可以,理到某一步,继续深入,回来后,接着理清除下面的步骤. ✿ 阅读本文的准备工作,预习一下Spr ...

  4. Spring Security 案例实现和执行流程剖析

    Spring Security Spring Security 是 Spring 社区的一个顶级项目,也是 Spring Boot 官方推荐使用的安全框架.除了常规的认证(Authentication ...

  5. SpringMVC执行流程及源码分析

    SpringMVC流程及源码分析 前言 ​ 学了一遍SpringMVC以后,想着做一个总结,复习一下.复习写下面的总结的时候才发现,其实自己学的并不彻底.牢固.也没有学全,视频跟书本是要结合起来一起, ...

  6. Tomcat笔记:Tomcat的执行流程解析

    Bootstrap的启动 Bootstrap的main方法先new了一个自己的对象(Bootstrap),然后用该对象主要执行了四个方法: init(); setAwait(true); load(a ...

  7. Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程

    在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),G ...

  8. debian下使用dynamic printk分析usb转串口驱动执行流程

    看了一篇文章<debug by printing>,文中提到了多种通过printk来调试驱动的方法,其中最有用的就是"Dynamic debugging". “Dyna ...

  9. asyncio源码分析之基本执行流程

    基于async关键字的原生协程 # 定义一个简单的原生协程cor async def cor(): print('enter cor') print('exit cor') print(type(co ...

  10. mybatis源码专题(2)--------一起来看下使用mybatis框架的insert语句的源码执行流程吧

    本文是作者原创,版权归作者所有.若要转载,请注明出处.本文以简单的insert语句为例 1.mybatis的底层是jdbc操作,我们先来回顾一下insert语句的执行流程,如下 执行完后,我们看下数据 ...

随机推荐

  1. Mp3文件标签信息读取和写入(Kotlin)

    原文:Mp3文件标签信息读取和写入(Kotlin) - Stars-One的杂货小窝 最近准备抽空完善了自己的星之小说下载器(JavaFx应用 ),发现下载下来的mp3文件没有对应的标签 也是了解可以 ...

  2. C#与Halcon联合编程之用PictureBox控件替代HWindowControl控件

    在使用HALCON和C#联合编程,有时候要使用halcon的HWindowControl控件,但是我发现,HWindowControl的图片显示控件,不能使用GDI+绘制ROI,不知道为什么,反正我测 ...

  3. JavaScrip基础学习笔记(一)

    一.三元表达式 1.1 什么是三元表达式 由三元运算符组成的式子我们称为三元表达式 1.2 语法结构 条件表达式 ? 表达式1 : 表达式2 1.3 执行思路 如果表达式为结果真 则返回表达式1的值, ...

  4. RNN的PyTorch实现

    官方实现 PyTorch已经实现了一个RNN类,就在torch.nn工具包中,通过torch.nn.RNN调用. 使用步骤: 实例化类: 将输入层向量和隐藏层向量初始状态值传给实例化后的对象,获得RN ...

  5. Referenced file contains errors (http://mybatis.org/dtd/mybatis-3-config.dtd). For more information, right click on the message in the Problems View and select "Show Details..."

    mybatis配置文件报错Referenced file contains errors mybatis的配置文件报错 The errors below were detected when vali ...

  6. 【每日一题】【字符串与数字互转】【去除空格】【大数处理】2021年12月12日-8. 字符串转换整数 (atoi)

    请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数). 函数 myAtoi(string s) 的算法如下: ...

  7. docker部署项目

    @ 目录 前言 一.下载安装docker: 1.前提工作 1.1 查看linux版本 1.2 yum包更新到最新 1.3 安装工具包 1.4 设置yum源并更新yum包索引 2.安装docker 2. ...

  8. kali2021.4a安装angr(使用virtualenv)

    在Linux中安装各种依赖python的软件时,最头疼的问题之一就是各个软件的python版本不匹配的问题,angr依赖python3,因此考虑使用virtualenv来安装angr Virtuale ...

  9. 如何通过C#合并Word文档?

    合并Word文档可以快速地将多份编辑好的文档合在一起,避免复制粘贴时遗漏内容,以及耗费不必要的时间,同时,也方便了人们阅读或者对其进行再次修改.例如,在我们进行团队作业的时候,每个人都会有不同的分工, ...

  10. 你的项目使用Optional了吗?

    1.基本概念 java.util.Optional<T>类本质上就是一个容器,该容器的数值可以是空代表一个值不存在,也可以是非空代表一个值存在. 2.获取对象 2.1 相关方法 2.2 案 ...