【Spring】深入解析 Spring AOP 核心概念:切点、连接点、通知、切面、通知类型和使用 @PointCut 定义切点的方法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


Spring AOP


下面我们再来详细学习 AOP,主要是以下几部分:

image-20250421182852776


Spring AOP 核心概念


image-20250421114620399


切点(Pointcut)


切点(Pointcut),也称之为“切入点”。

Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述),告诉程序对哪些方法来进行功能增强。

image-20250421112458255

上面的表达式 execution(* com.example.demo.controller.*.*(..)) 就是切点表达式。


连接点(Join Point)


满足切点表达式规则方法,就是连接点。也就是可以被 AOP 控制的方法。

以入门程序举例,所有 com.example.demo.controller 路径下的方法,都是连接点。

image-20250421114123462

上述 BookController 中的方法都是连接点。


image-20250421152705933


切点和连接点的关系:

  • 连接点是满足切点表达式元素切点可以看做是保存了众多连接点的一个集合。 比如:
  • 切点表达式:全体偶像练习生
  • 连接点就是:蔡徐坤、范丞丞等各个偶像练习生

通知(Advice)


通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能最终体现为一个方法)。

比如上述程序中记录业务方法的耗时时间,就是通知

image-20250421114310008

在 AOP 面向切面编程当中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容。


切面(Aspect)


切面(Aspect)= 切点(Pointcut)+ 通知(Advice)。

通过切面就能够描述当前 AOP 程序需要针对于哪些方法,在什么时候执行什么样的操作。

切面既包含了通知逻辑的定义,也包括了连接点的定义。

image-20250421114337427

切面所在的类,我们一般称为切面类(被 @Aspect 注解标识的类)。


通知类型


上面我们讲了什么是通知,接下来学习通知的类型@Around 就是其中一种通知类型,表示环绕通知

image-20250421114423462


Spring 中 AOP通知类型有以下几种:

注解名称描述
@Around环绕通知,此注解标注的通知方法在目标方法前、后都被执行。
@Before前置通知,此注解标注的通知方法在目标方法前被执行。
@After后置通知,此注解标注的通知方法在目标方法后被执行的,无论是否有异常都会执行。
@AfterReturning返回后通知,此注解标注的通知方法在目标方法后被执行的,有异常不会执行。
@AfterThrowing异常后通知,此注解标注的通知方法在发生异常后执行。

接下来我们通过代码来加深对这几个通知的理解:

为方便学习,我们可以新建一个项目,使用 web+lombok 依赖,删除 pom.xml 文件多余部分:

image-20250421130421846


添加 AOP 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

先创建测试类接口:

image-20250421130303873


添加连接点:

image-20250421131054719


创建切面(需引入 AOP 依赖):

image-20250421132632270

@Slf4j
@Aspect
@Component
public class AspectDemo1 {
    /**
     * @Around(环绕通知):此注解标注的通知方法在目标执行前,后都被执行
     */
    @Around("execution(* com.bit.springaopdemo.Controller.*.*(..))")
    public Object TimeAspect(ProceedingJoinPoint pjp) throws Throwable {
        log.info("目标方法执行前.......");
        Object result = pjp.proceed();
        log.info("目标方法执行后.......");
        return result;
    }

    /**
     * @Before(前置通知),此注解标注的通知方法在目标方法前被执行
     */
    @Before("execution(* com.bit.springaopdemo.Controller.*.*(..))")
    public void doBefore(){
        log.info("doBefore.......");
    }

    /**
     * @After(后置通知),此注解标注的通知方法在目标方法后被执行的,无论是否有异常都会执行。
     */
    @After("execution(* com.bit.springaopdemo.Controller.*.*(..))")
    public void doAfter(){
        log.info("doAfter.......");
    }

    /**
     * @AfterReturning(返回后通知),此注解标注的通知方法在目标方法后被执行的,有异常不会执行。
     */
    @AfterReturning("execution(* com.bit.springaopdemo.Controller.*.*(..))")
    public void doAfterReturning(){
        log.info("AfterReturning.......");
    }

    /**
     * @AfterThrowing(异常后通知),此注解标注的通知方法在发生异常后执行。
     */
    @AfterThrowing("execution(* com.bit.springaopdemo.Controller.*.*(..))")
    public void doAfterThrowing(){
        log.info("AfterThrowing.......");
    }
}

运行程序,观察日志:


1、正常运行的情况:


http://127.0.0.1:8080/test/t1

image-20250421150515643


观察日志:

image-20250421150603225


这个日志中,我们还是不知道目标方法 t1() 是什么时候执行的,再打上一个日志,重新运行程序,并发送请求:

image-20250421150959335


程序正常运行的情况下,@AfterThrowing 标识的通知方法不会执行。

image-20250421153218053

从上图也可以看出来,@Around 标识的通知方法包含两部分,一个“前置逻辑”,一个“后置逻辑”


其中“前置逻辑”会先于 @Before 标识的通知方法执行,“后置逻辑”会晚于 @After 标识的通知方法执行:

image-20250420172341060


2、异常时的情况:


为了方便观察,我们给 Controller 中的每个接口都加上 log 日志:

image-20250421151534707


测试方法 t1() 是没有异常的情况,接下来,我们来测试目标方法存在异常的情况:

image-20250421151755261


观察日志:

image-20250421151927017


程序发生异常的情况下:

  • @AfterReturning 标识的通知方法不会执行;

  • @AfterThrowing 标识的通知方法执行了;

image-20250421152232823


如果我们在环绕通知 @Around 中,使用 try....catch .....连接点(目标方法)的异常进行捕获,能否达到 @AfterThrowing 的功能呢?

image-20250421160235500


@Around 环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会再执行了(因为原始方法调用出异常了)。

image-20250421154553448


image-20250420172434125


注意事项:

  • @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行其他通知不需要考虑目标方法执行

  • @Around 环绕通知 的方法返回值,必须指定为 Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。

  • 一个切面类可以有多个切点


定义切点@PointCut


上面代码存在一个问题,就是存在大量重复的切点表达式:

execution(* com.example.demo.controller.*.*(..))


Spring 提供了 @PointCut 注解,把公共的切点表达式提取出来,需要用到时引用该切入点表达式即可。

上述代码就可以修改为:

image-20250421163815187


image-20250421164457806


当切点定义使用 private 修饰时,仅能在当前切面类中使用。

image-20250421164719422


其他切面类也要使用当前切点定义时,就需要把 private 改为 public

image-20250421165159178


引用方式为:全限定类名.方法名()

image-20250421165100748


在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值