实用指南: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文件导入依赖。
org.springframework.boot
spring-boot-starter-aop

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
{
@Autowired
MathCalculator mathCalculator;
@Test
public 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. 代理对象执行了增强器链相关操作。
    在这里插入图片描述
posted @ 2025-09-02 20:59  wzzkaifa  阅读(21)  评论(0)    收藏  举报