Spring AOP面向切面的底层原理、注解、切入点表达式、连接点获取方法名参数值等
Spring AOP注解原理与实例
参考视频:https://www.bilibili.com/video/BV14WtLeDEit/?p=41&share_source=copy_web&vd_source=053075eb8bd04ebb7bc161d3f4386d4
AOP注解
- @Before():不管方法是否执行成功,都会执行
- @AfterReturning():方法执行成功,返回结果才会执行
- @AfterThrowing():方法执行失败,抛出异常才会执行
- @After():不管方法是否执行成功,都会执行
执行顺序
- @Before注解:先于方法之前,执行,最先执行。
- 被代理类的方法执行
- @AfterReturning:方法返回结果之后,执行
- @AfterThrowing:方法返回结果之后,执行。与@AfterReturning互斥,同一时间只能执行一个,要么成功,要么时报。
- @After注解:方法结果返回之后,执行,最后执行。
通知方法执行顺序
- 正常链路:前置通知->目标方法->返回通知->后置通知
@Before->目标方法->@AfterReturning
->@After - 异常链路:前置通知->目标方法->异常通知->后置通知
@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,后置
步骤
- 导入AOP依赖。
- 编写切面Aspect。
- 编写通知方法。
- 指定切入点表达式。
- 测试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;}}
图解:
多个切面类为什么一定要求环绕切面类要重新抛出异常?
- 当我们的环绕切面类,执行方法抛出异常时。
- 如果不抛出异常,则其外层切面类,会将其当作返回通知进行处理。
- 也就导致实际异常,结果做了返回处理。
底层原理
- 增强器链:切面中的所有通知方法其实是增强器,他们被组织成一个链路放到集合中,目标方法真正执行前后,会去增强器链中执行哪些需要提前执行的方法。
AOP的底层原理是什么?
- Spring会为每个被切面切入的组件创建代理对象(Spring CGLIB 创建的代理对象,无视接口,没有接口也能创建代理对象)。
- 代理对象中保存了,切面类里面所有通知方法构成的增强器链。
- 目标方法执行时,会先执行增强器链中拿到需要提前执行的通知方法去执行。
代理对象信息
不使用切面的情况下
- 由容器直接注入mathCalculator目标对象,结果输出为目标对象的信息。
使用切面的情况下
- 由容器直接注入mathCalculator目标对象。
- 由于使用了切面类,对目标方法进行了代理。
- 结果输出为代理对象的信息。
- 代理对象执行了增强器链相关操作。