跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Spring AOP 注解实现详解

Spring AOP 的基本概念及注解实现方式。涵盖了切点、连接点、通知、切面等核心概念,详细说明了五种通知类型(Around、Before、After 等)的使用场景。通过代码示例展示了如何配置依赖、定义切面、使用公共切点引用以及设置切点优先级。最后讲解了 execution 和 @annotation 两种切点表达式的语法及应用,帮助开发者掌握基于注解的 Spring AOP 编程模式。

GRACE Grace发布于 2026/3/30更新于 2026/7/1450 浏览
Spring AOP 注解实现详解

一、AOP 与 Spring AOP

AOP:Aspect Oriented Programming(面向方面编程)。是一种对某一类事情集中处理的思想。

Spring AOP:就是对 AOP 思想的一种实现。

二、Spring AOP 简单实现

我们简单实现一个统计每个接口的用时。

引入依赖:

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

写 AOP 实现:

  • 类使用注解@Aspect 修饰
  • 方法参数为 ProceedingJoinPoint 类,代表要实现的方法(只能在 Around 通知下写)
  • 方法使用注解@Around,参数是对应的路径的切点
  • ProceedingJoinPoint 的参数执行 proceed 方法。
package com.example.library.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
public class TimeAspect {
    @Around("execution(* com.example.library.controller.*.*(..) )")
    public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 开始时间
        long start = System.currentTimeMillis();
        // 执行方法
        Object result = proceedingJoinPoint.proceed();
        // 结束时间
        long end = System.currentTimeMillis();
        log.info("执行时间:" + (end - start) + "ms");
        return result;
    }
}

三、详解 Spring AOP

3.1 Spring AOP 核心概念

Spring AOP 核心概念:切点,连接点,通知,切面。

我们以上面的代码来介绍。

3.1.1 切点(Pointcut)

切点:就是告诉程序哪些方法需要使用到接下来的功能。
上面的@Around 注解的参数就是切点表达式。

文章配图

3.1.2 连接点(Join Point)

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

就像上面的代码的连接点就是:com.example.library.controller 路径下的所有方法。

切点和连接点的关系:连接点是满足切点表达式的元素。切点可以看做是保存了众多连接点的一个集合。

3.1.3 通知(Advice)

通知:这个 Spring AOP 方法要实现的功能就是通知。

就像上面的实现一个统计每个接口的用时的需求,就是通知。

文章配图

3.1.4 切面(Aspect)

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

通过切面就能够描述当前 AOP 程序需要针对于哪些方法,在什么时候执行什么样的操作。
切面既包含了通知逻辑的定义,也包括了连接点的定义。

文章配图

3.2 通知类型

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

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

效果:

package com.example.demoaop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class TestAspect {
    // 前置通知
    @Before("execution(* com.example.demoaop.*.*(..) )")
    public void testBefore() {
        log.info("Before 方法执行前");
    }

    // 后置通知
    @After("execution(* com.example.demoaop.*.*(..) )")
    public void testAfter() {
        log.info("After 方法执行后");
    }

    // 返回后通知
    @AfterReturning("execution(* com.example.demoaop.*.*(..) )")
    public void testAfterReturning() {
        log.info("AfterReturning 返回后通知");
    }

    // 抛出异常后通知
    @AfterThrowing("execution(* com.example.demoaop.*.*(..) )")
    public void testAfterThrowing() {
        log.info("AfterThrowing 抛出异常后通知");
    }

    // 环绕通知
    @Around("execution(* com.example.demoaop.*.*(..) )")
    public void testAround(ProceedingJoinPoint pjp) throws Throwable {
        log.info("Around 方法执行前");
        Object proceed = pjp.proceed();
        log.info("Around 方法执行后");
    }
}

文章配图

3.3 公共切点引用@PointCut

当我们的切点表达式是一样的时候,像上面我们还是在每一个通知类型的注解中,都使用了相同的表达式。
我们就可以使用方法注解@PointCut 将切点表达式提取出来,然后后面使用只需要写方法名即可。
在其他切点类中也可以调用,需要将@PointCut 注解所在类的路径写出来。

package com.example.demoaop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class TestAspect {
    @Pointcut("execution(* com.example.demoaop.*.*(..) )")
    public void pc() {}

    // 前置通知
    @Before("pc()")
    public void testBefore() {
        log.info("TestAspect Before 方法执行前");
    }

    // 后置通知
    @After("pc()")
    public void testAfter() {
        log.info("TestAspect After 方法执行后");
    }
}
package com.example.demoaop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class TestAspect2 {
    // 前置通知
    @Before("com.example.demoaop.TestAspect.pc()")
    public void testBefore() {
        log.info("TestAspect2 Before 方法执行前");
    }

    // 后置通知
    @After("pc()")
    public void testAfter() {
        log.info("TestAspect2 After 方法执行后");
    }
}

结果:
可以看见生效了,而且在其他切点类中只有加上了路径的才生效了。

文章配图

3.4 切点优先级@Order

我们定义 3 个一样的切点类,看他们的输出顺序:

文章配图

存在多个切面类时,默认按照切面类的类名字母排序:

  • @Before 通知:字母排名靠前的先执行
  • @After 通知:字母排名靠前的后执行
    但这种方不方便管理,我们的类名更多还是具备一定含义的。
    Spring 给我们提供了一个新的注解,来控制这些切面通知的执行顺序:@Order

我们将切点类的优先级换一下:

@Component
@Slf4j
@Aspect
@Order(1)
public class TestAspect3 {
    // 前置通知
    @Before("com.example.demoaop.TestAspect.pc()")
    public void testBefore() {
        log.info("TestAspect3 Before 方法执行前");
    }

    // 后置通知
    @After("com.example.demoaop.TestAspect.pc()")
    public void testAfter() {
        log.info("TestAspect3 After 方法执行后");
    }
}
@Component
@Slf4j
@Aspect
@Order(2)
public class TestAspect2 {
    // 前置通知
    @Before("com.example.demoaop.TestAspect.pc()")
    public void testBefore() {
        log.info("TestAspect2 Before 方法执行前");
    }

    // 后置通知
    @After("com.example.demoaop.TestAspect.pc()")
    public void testAfter() {
        log.info("TestAspect2 After 方法执行后");
    }
}
@Component
@Slf4j
@Aspect
@Order(3)
public class TestAspect {
    @Pointcut("execution(* com.example.demoaop.*.*(..) )")
    public void pc() {}

    // 前置通知
    @Before("pc()")
    public void testBefore() {
        log.info("TestAspect Before 方法执行前");
    }

    // 后置通知
    @After("pc()")
    public void testAfter() {
        log.info("TestAspect After 方法执行后");
    }
}

执行结果:

文章配图

规律:
@Order 注解标识的切面类,执行顺序如下:

  • @Before 通知:数字越小先执行
  • @After 通知:数字越大先执行

像下图的表示,箭头代表执行过程:

文章配图

3.5 切点表达式

切点表达式用来描述切点,常有以下两种类型的切点表达式:execution 和 @annotation

3.5.1 execution

语法:

execution(<访问修饰限定符> <返回类型> <包名。类名。方法名 (方法参数)> <异常>)

含义:

  • 访问修饰限定符:表示切点对应的方法的访问修饰限定符
  • 返回类型:表示切点对应的方法的返回类型
  • 包名。类名。方法名 (方法参数):表示切点对应的方法的路径及参数
  • 异常:表示切点对应的方法抛出的异常
  • 访问修饰限定符 和 异常 可以省略

切点表达式支持通配符表达:

  1. * 匹配任意字符,只匹配一个元素 (返回类型,包,类名,方法或者方法参数)
    1.1. 包名使用 * 表示任意包 (一层包使用一个 * )
    1.2. 类名使用 * 表示任意类
    1.3. 返回值使用 * 表示任意返回值类型
    1.4. 方法名使用 * 表示任意方法
    1.5. 参数使用 * 表示一个任意类型的参数
  2. .. 匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数
    2.1. 使用 .. 配置包名,标识此包以及此包下的所有子包
    2.2. 可以使用 .. 配置参数,任意个任意类型的参数

例子:

  • TestController 下的 public 修饰,返回类型为 String 方法名为 t1 的无参方法
execution(public String com.example.demo.TestController.t1())
  • 匹配 TestController 下的所有无参方法
execution(*com.example.demo.TestController.*())
  • 匹配 controller 包下所有的类的所有方法
execution(* com.example.demo.controller.*.*(..))
3.5.2 @annotation

当我们要落实到不同类下个几个方法,用上面的 execution 就有点捉襟见肘。
我们就可以使用自定义注解的方式以及另一种切点表达式 @annotation 来描述这一类的切点。

自定义注解:

  • 然后就跟我们前面使用的注解一样包含,生命周期@Retention,作用范围@Target,交给 Spring 管理。

在自定义类的时候选择 annotation:

文章配图

@Component
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {}

定义切面类:

  • 使用@Aspect 注解修饰,
  • 交给 Spring 管理
  • 在通知类型的注解中使用:@annotation(自定义注解路径) 作为参数。
@Slf4j
@Component
@Aspect
public class MyAspectDemo {
    @Around("@annotation(com.example.demoaop.MyAspect)")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("annotation 运行前");
        pjp.proceed();
        log.info("annotation 运行后");
    }
}

通过上面的方法,使用了自定义注解修饰的方法,就可以添加切面类的通知。

@RequestMapping("/test")
@RestController
@Slf4j
public class Test {
    @RequestMapping("/f1")
    public String f1() {
        log.info("f1");
        return "s1";
    }

    @MyAspect
    @RequestMapping("/f2")
    public Integer f2() {
        log.info("f2");
        return 1;
    }

    @RequestMapping("/f3")
    public Boolean f3() {
        log.info("f3");
        return false;
    }
}

访问 f2 f1 f3 的结果:

文章配图

目录

  1. 一、AOP 与 Spring AOP
  2. 二、Spring AOP 简单实现
  3. 三、详解 Spring AOP
  4. 3.1 Spring AOP 核心概念
  5. 3.1.1 切点(Pointcut)
  6. 3.1.2 连接点(Join Point)
  7. 3.1.3 通知(Advice)
  8. 3.1.4 切面(Aspect)
  9. 3.2 通知类型
  10. 3.3 公共切点引用@PointCut
  11. 3.4 切点优先级@Order
  12. 3.5 切点表达式
  13. 3.5.1 execution
  14. 3.5.2 @annotation
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 仓库管理系统:物品类型管理功能实现
  • Java 高性能开发实战:Redis 7 持久化机制详解
  • Java 核心基础:深入理解 Spring IoC 容器与依赖注入
  • 从 ChatGPT 到 AIGC:智能创作与应用赋能深度解析
  • C语言算法练习:从两数之和到寻找峰值
  • AI 绘画技术解析与商业化变现实战指南
  • Python 函数核心概念与实战指南
  • LangChain 核心组件与 LLM 应用开发实战
  • 即答侠 InterviewAssistant 技术解析:AI 面试辅助与简历优化实战
  • Python+Matplotlib 大数据可视化高效解决方案
  • Node.js + uni-app 运动健康 App 计算机毕业设计
  • StructBERT-Large 实战教程:单句对多句批量检索模式扩展开发指南
  • OpenClaw 架构原理与实战部署指南
  • Vue3 + Python 体育赛事发布与在线购票选座系统设计
  • DeepSeek 结合通义万相实现 AI 视频高效制作
  • 多旋翼无人机电源系统详解
  • VTJ.PRO:Vue3 AI 低代码引擎如何平衡效率与自由
  • AI 绘画工作台:Z-Image-Turbo 云端协作部署指南
  • 使用 Trae IDE 将 Figma 设计稿转化为前端代码的技术解析与实践
  • C++ STL list 容器特性与底层原理

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online