一文拆解Spring AOP 核心概念与通知类型

 更新时间:2026年11月10日 09:24:23   作者:驭渊的小故事  
‌Spring AOP的核心概念是切面、连接点、切点、通知、目标对象、代理对象和织入,其中通知分为前置、返回、异常、最终、环绕五种类型,‌本文介绍Spring AOP 核心概念与通知类型详解,感兴趣的朋友一起看看吧

系列导航:上一篇跑通了第一个 AOP 程序,本篇拆解 AOP 的核心术语,并重点推演正常和异常两种流程下,五种通知类型的执行顺序。

1. 概念推导:切面是如何运转的?

AOP 的执行逻辑可以概括为三步:找准目标 → 施加增强 → 组成切面。

  • 怎么找准目标?
    • 我们需要制定一个规则,例如“拦截 controller 包下的所有方法”。这个规则叫切点(Pointcut)。
  • 目标是哪些?
    • 被规则命中的真实方法,例如 queryBookById,叫连接点(JoinPoint)。切点可以理解为保存了众多连接点的集合。
  • 施加什么增强?
    • 拦截到目标方法后要做的工作,例如统计耗时,叫通知(Advice)。
  • 组合起来叫什么?
  • 切点 + 通知 = 切面(Aspect)。切面既告诉程序“对谁做”,也告诉程序“做什么”和“什么时候做”。

Spring AOP 核心概念关系图

2. 流程推演:五种通知类型的执行轨迹

Spring 提供了 5 种通知类型:

通知类型含义
@Around环绕通知,目标方法前后都执行
@Before前置通知,目标方法前执行
@AfterReturning返回后通知,正常返回后执行,有异常不执行
@AfterThrowing异常后通知,目标方法抛异常后执行
@After后置通知,目标方法后执行,无论是否异常都执行,类似 finally

为了看清执行顺序,我们写一个包含 5 种通知的切面 AspectDemo,拦截 TestController。

全套演示核心代码(AspectDemo.java)

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo {
    // 公共切点:抽取表达式,所有通知复用
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    private void pt(){}
    @Before("pt()")
    public void doBefore(JoinPoint joinPoint){
        log.info("@Before 前置通知执行");
    }
    @AfterReturning("pt()")
    public void doAfterReturning(JoinPoint joinPoint){
        log.info("@AfterReturning 返回通知执行");
    }
    @AfterThrowing("pt()")
    public void doAfterThrowing(JoinPoint joinPoint){
        log.info("@AfterThrowing 异常通知执行");
    }
    @After("pt()")
    public void doAfter(JoinPoint joinPoint){
        log.info("@After 后置通知执行");
    }
    @Around("pt()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        log.info("@Around 环绕【前置】逻辑");
        Object result = null;
        try {
            // 执行目标方法
            result = pjp.proceed();
        } catch (Throwable throwable) {
            throw throwable;
        }
        log.info("@Around 环绕【后置】逻辑");
        return result;
    }
}

测试 Controller 代码 TestController.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
    // 正常执行接口
    @GetMapping("/t1")
    public String t1(){
        log.info("===== 目标方法 t1 正在执行 =====");
        return "success";
    }
    // 会抛出异常的接口
    @GetMapping("/t2")
    public String t2(){
        log.info("===== 目标方法 t2 正在执行 =====");
        int a = 10 / 0;
        return "success";
    }
}

流程推演 1:目标方法正常执行(/test/t1)

  • 请求进入,首先触发 @Around 的前置逻辑。
  • 接着触发 @Before。
  • 执行目标方法 t1()。
  • 目标方法正常返回,触发 @AfterReturning。
  • 无论是否异常都要收尾,触发 @After。
  • 最后回到 @Around,执行后置逻辑。

正常运行时,@AfterThrowing 不会执行。

正常执行时的通知顺序

流程推演 2:目标方法发生异常(/test/t2,代码为int a = 10 / 0;)

  • 请求进入,触发 @Around 的前置逻辑。
  • 接着触发 @Before。
  • 执行目标方法 t2(),抛出 ArithmeticException。
  • 异常被捕获,触发 @AfterThrowing。
  • 依然要执行收尾工作,触发 @After。
  • 因为 pjp.proceed() 抛出异常,@Around 的后置逻辑不再执行,@AfterReturning 也不会执行。

异常执行时的通知顺序

核心注意事项

  • @Around 的返回值必须是 Object,因为它要接收 pjp.proceed() 的返回值;否则目标方法即使有返回值,也无法传给调用方。
  • 除 @Around 外,其他四个通知都只是“旁观者”,不需要在参数中写 ProceedingJoinPoint,它们无需控制目标方法执行。
  • @Around 需要调用 proceed() 让原始方法执行;其他通知不需要考虑目标方法执行。

3. 流程优化:重复表达式的痛与@Pointcut

痛点:上面的 5 个通知,每个注解里都写了一长串 execution(* com.example.demo.controller.*.*(..))。如果哪天要改拦截规则,得改 5 次。

方案:用 @Pointcut 提取公共切点表达式。

@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){} // 定义切点
@Before("pt()") // 引用切点
public void doBefore() { ... }

流程变化:Spring 解析时,会先找到 pt() 这个空方法,拿到里面的表达式去匹配,再把匹配结果应用到 doBefore 等通知上。

注意:@Pointcut 方法使用 private 时,仅当前切面类可用;其他切面类要引用时,需要改为 public,引用方式为 全限定类名.方法名()。

4. 流程优化:多切面时的执行顺序与@Order

痛点:如果 AspectDemo2、AspectDemo3 都拦截了同一个方法,谁先执行?

默认规则:Spring 默认按切面类名的字母排序。@Before 字母靠前的先执行,@After 字母靠前的后执行。

问题:类名不可控,逻辑容易混乱。

方案:使用 @Order(数字)。数字越小,优先级越高。

  • @Before 阶段:优先级高的切面先执行。
  • @After 阶段:优先级高的切面后执行,因为它被包在最外层。

这就像穿衣服和脱衣服:先穿的最晚脱。

@Order 的洋葱模型

到此这篇关于一文拆解Spring AOP 核心概念与通知类型的文章就介绍到这了,更多相关Spring AOP概念内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java13 明天发布(最新最全新特性解读)

    Java13 明天发布(最新最全新特性解读)

    这篇文章主要介绍了Java13 明天发布,最新最全新特性解读,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • SpringCloud 中使用 Ribbon的方法详解

    SpringCloud 中使用 Ribbon的方法详解

    本文重点给大家介绍SpringCloud 中使用 Ribbon的方法,本文通过实例文字相结合的形式给大家介绍的非常详细,需要的朋友可以参考下
    2018-10-10
  • RocketMQ Namesrv架构工作原理详解

    RocketMQ Namesrv架构工作原理详解

    这篇文章主要为大家介绍了RocketMQ Namesrv架构工作原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • 关于Java中的实体类要 implements Serializable的原因分析

    关于Java中的实体类要 implements Serializable的原因分析

    这篇文章主要介绍了Java中的实体类为什么要 implements Serializable,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • Java实现在线五子棋对战游戏(人机对战)

    Java实现在线五子棋对战游戏(人机对战)

    这篇文章主要为大家详细介绍了如何利用Java语言实现在线五子棋对战游戏(人机对战),文中的实现步骤讲解详细,感兴趣的可以尝试一下
    2022-09-09
  • SpringBoot如何获取Get请求参数详解

    SpringBoot如何获取Get请求参数详解

    SpringBoot为我们封装了许多简便的获取请求参数的方法,下面这篇文章主要给大家介绍了关于SpringBoot如何获取Get请求参数的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • servlet之web路径问题_动力节点Java学院整理

    servlet之web路径问题_动力节点Java学院整理

    这篇文章主要为大家详细介绍了servlet之web路径问题的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • MyBatis执行Sql的流程实例解析

    MyBatis执行Sql的流程实例解析

    这篇文章主要介绍了MyBatis执行Sql的流程实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • spring boot集成p6spy的最佳实践

    spring boot集成p6spy的最佳实践

    这篇文章主要介绍了spring boot集成p6spy的最佳实践-p6spy-spring-boot-starter的项目案例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-02-02
  • IDEA maven父工程pom.xml使用及说明

    IDEA maven父工程pom.xml使用及说明

    MavenPOM是Maven工程的基本工作单元,父工程的pom文件中使用<packaging>pom</packaging>表示使用maven分模块管理,父级项目通常使用pom作为packaging,并通过<properties>和<dependencyManagement>统一管理项目的版本号和依赖
    2026-01-01

最新评论