当前位置: 首页 > news >正文

Spring AOP---面向切面编程由认识到使用

1. AOP

AOP(Aspect-Oriented Programming), 是一种思想, 面向切面编程。

在前文统一异常处理,统一结果返回就是使用了这一思想(都是在集中处理某一类事情, 但又不影响原有代码的正常运行),但他们不是AOP,只是应用了这一思想。

共同点就是:不修改目标方法,而达到了对目标方法功能的增强(就像MybatisPlus对Mybatis) 

AOP是一种思想, 实现它的方式有很多: SpringAOP, AspectJ, CGLIB, ....等

Spring 共有两大核心机制, 一个是 Spring IoC, 一个就是 Spring AOP.

在这句话中,第一个 "Spring" 指的是 Spring Framework,而不是 Spring Boot。

原因是,Spring IoC(控制反转)和 Spring AOP(面向切面编程)是 Spring Framework 的两大核心特性。这些特性是 Spring Framework 的基础,Spring Boot 则是在 Spring Framework 的基础上进行封装和简化配置,使得开发者能更快速地构建应用。

Spring IoC 主要负责对象的管理和依赖注入。

Spring AOP 用于面向切面编程,通过代理机制提供横切关注点(如日志、事务管理等)。

Spring Boot 作为 Spring Framework 的扩展,简化了配置和开发流程,但核心机制(如 IoC 和 AOP)依然是基于 Spring Framework 的。所以当提到 "Spring 的核心机制" 时,通常是指 Spring Framework 中的 IoC 和 AOP。 

关于面向切面编程的讲解会在应用中体现 

2. Spring AOP 入门

2.1 引入 Spring AOP 依赖

Spring AOP 依赖不能在创建项目时引入, 必须手动引入

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

2.2 Spring AOP 简单使用

我们使用 Spring AOP 编写一个程序, 记录接口的执行时长.

@Slf4j
@Aspect
@Component
public class TimeAspect {//公共切点表达式@Pointcut("@annotation(com.cym.spring_aop.aspect.MyAspect)")private void pt(){}@Around("pt()")public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//记录方法执行前的时间long begin = System.currentTimeMillis();//方法执行Object result = pjp.proceed();//记录方法执行后的时间long end = System.currentTimeMillis();log.info(pjp.getSignature() + ",耗费时间:{}" , end - begin);return result;}
}

如上图所示, 通过 Spring AOP, 我们只需在外部通过 Spring AOP 就可以获取到接口的执行时长. 达到了 "获取执行时长" 这一功能和业务代码的解耦

如果不使用 Spring AOP, 我们就需要在每个接口的起始位置和结束位置获取时间戳, 再计算执行时长, 这样不仅入侵了业务代码, 还需要手动对每个要实现这个功能的接口都编写这些代码(对于懒人是一定不能接受的吧)

3.Spring AOP 详解

3.1 核心概念

Spring AOP 有以下 4 个核心概念:

  1. 切点
  2. 连接点
  3. 通知
  4. 切面

举个例子:
假如你要在所有 Controller 方法执行前打印日志,那么:

切点(Pointcut) = "所有 Controller 里的方法"(切点可以看做是保存了众多连接点的⼀个集合

连接点(JoinPoint)= "Controller 中某个具体的方法"(满⾜切点表达式规则的⽅法

通知(Advice) = "方法执行前,打印日志"(对他说:开工吧)

切面(Aspect) = "拦截所有 Controller 方法,在执行前打印日志"

切⾯(Aspect) = 切点(Pointcut) + 通知(Advice)

 

3.1.1 切点


切点(Pointcut)本质上只是一个筛选规则, 它不会影响代码执行, 也不会真正“拦截”任何东西, 它只是告诉 Spring 要对哪些方法进行拦截, 对哪些方法生效.

3.1.1.1 @Pointcut 定义切点

切点 = @Pointcut 注解(声明切点) + 切点表达式(定义规则)


切点表达式是切点的一部分, 它决定了切点的“筛选规则”.

切点通过切点表达式定义一套规则, 这个规则表名了对哪些方法生效/拦截哪些方法(是一个集合), 描述哪些方法可能成为连接点

可以选择直接在@Around中写表达式:

@Around("execution(* com.cym.spring_aop.controller.*.*(..))")

也可以写在 pt()中:(可以复用,推荐)

    //公共切点表达式@Pointcut("execution(* com.cym.spring_aop.controller.*.*(..))")private void pt(){}@Around("pt()")

 还可以在其他切面中使用(使用时, 需要写出这个切点的全限定名):

com.cym.spring_aop.aspect.TimeAspect.pt()
3.1.1.2 切点表达式


常见的切点表达式有以下两种:

execution: 根据方法的签名来匹配 (如上图所示)
@annotation(......): 根据注解匹配


execution 表达式:

语法:

其中, 访问限定修饰符和异常可以省略.

以execution(* com.cym.spring_aop.controller.*.*(..))为例:

    /*** 1.TestController 下的 public修饰, 返回类型为String ⽅法名为t1, ⽆参⽅法* execution(public String com.example.demo.controller.TestController.t1())* 2.省略访问修饰符* execution(String com.example.demo.controller.TestController.t1())* 3.匹配所有返回类型* execution(* com.example.demo.controller.TestController.t1())* 4.匹配TestController 下的所有⽆参⽅法* execution(* com.example.demo.controller.TestController.*())* 5.匹配TestController 下的所有⽅法* execution(* com.example.demo.controller.TestController.*(..))* 6.匹配controller包下所有的类的所有⽅法* execution(* com.example.demo.controller.*.*(..))* 7.匹配所有包下⾯的TestController* execution(* com..TestController.*(..))* 8.匹配com.example.demo包下, ⼦孙包下的所有类的所有⽅法* execution(* com.example.demo..*(..))*/

@annotation 表达式:


通过 execution 定义切点表达式, Spring AOP 拦截的是符合方法签名规则的方法.

而通过 @annotation 定义切点表达式, Spring AOP 拦截的是标注了特定注解的方法(可以是自定义注解, 也可以是 Spring 提供的注解), 因此更加灵活.

@annotation 中, 需要写出该注解的完全限定名.

1.第一步定义自定义注解

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

@Target(ElementType.METHOD):标识该注解只能作用于方法

@Retention(RetentionPolicy.RUNTIME):表示注解只存活在运行时,在编译好的文件中不存在

2.在需要拦截的方法前加上该注解

3.根据该注解定义切点表达式

    @Pointcut("@annotation(com.cym.spring_aop.aspect.MyAspect)")private void pt(){}@Around("pt()")

3.1.2 连接点

包含在切点表达式中的某个具体的方法, 在程序执行过程中实际被执行的那个方法, 就是一个连接点(即目标方法).

幸运儿,在增强中间执行的方法

  • 切点(Pointcut)是一个筛选规则,用来定义哪些方法(连接点)会被 AOP 代理。

  • 连接点(Join Point)是具体的方法,符合切点规则的方法就是连接点。

3.1.3 通知(Advice)


通知(Advice), 就是 AOP 拦截到目标方法(连接点)后, 具体要做的事/具体要执行的逻辑.

简单来说, 通知就是决定拦截后要做什么事情 .

切点(Pointcut) 只是一个“筛选规则”,它决定哪些方法(连接点)需要被拦截,但它本身不执行任何逻辑.

通知(Advice) 是真正 “干活的人”,它决定拦截后要做什么事情(比如打印日志、权限校验、事务管理等)

3.1.3.1 通知类型

Spring AOP 提供了 5 种常见的通知,不同的通知类型, 执行的时机不同:

• @Around: 环绕通知, 此注解标注的通知⽅法在⽬标⽅法前, 后都被执⾏

• @Before: 前置通知, 此注解标注的通知⽅法在⽬标⽅法前被执⾏

• @After: 后置通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, ⽆论是否有异常都会执⾏

• @AfterReturning: 返回后通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏,

有异常不会执⾏

• @AfterThrowing: 异常后通知, 此注解标注的通知⽅法发⽣异常后执⾏

代码演示:

    /*** 前置通知*/@Before("execution(* com.cym.spring_aop.controller.*.*(..))")public void doBefore() {System.out.println("执行doBefore方法");}/*** 后置通知*/@After("execution(* com.cym.spring_aop.controller.*.*(..))")public void doAfter() {System.out.println("执行doAfter方法");}/***抛出异常后通知*/@AfterThrowing("execution(* com.cym.spring_aop.controller.*.*(..))")public void doAfterThrowing() {System.out.println("执行doAfterThrowing方法");}/*** 返回后通知*/@AfterReturning("execution(* com.cym.spring_aop.controller.*.*(..))")public void doAfterReturning() {System.out.println("执行doAfterReturning方法");}/*** 返回后通知*/@Around("execution(* com.cym.spring_aop.controller.*.*(..))")public Object Around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("执行Around方法前..");Object result = pjp.proceed();System.out.println("执行Around方法后..");return result;}

 程序正常运行的情况下, @AfterThrowing 标识的通知方法不会执行, 只有抛出异常时, 该通知方法才会执行.

正常执行:

发现执行顺序:@Around   >  @doBefore  >  @doAferReturning  >  @doAfter  >  @Around

接下来我们添加一个异常 执行一下:

发现执行顺序:@Around   >  @doBefore  >  @doAferThrowing  >  @doAfter 

3.1.4 切面优先级

Spring AOP 允许多个切面作用于同一个目标方法.

当多个切面类, 作用于同一个目标方法(连接点)时, 切面之间是有优先级的:

  • 先执行优先级高的切面中的通知, 后执行优先级低的切面中的通知.

默认的切面优先级是按照名称来排序的:

不加优先级:

加了优先级 

@Order(1)
@Slf4j
@Aspect
@Component
public class Aspect2 {
...
}
@Order(2)
@Slf4j
@Aspect
@Component
public class Aspect3 {
...
}

注意: 

对于 JDK 代理. Spring AOP 只对 public 修饰的方法生效,即切点匹配的目标方法必须是 public, 切面的通知才会生效.
对于 CGLib 代理, Spring AOP 对非 private 非 final 修饰的方法生效,即切点匹配的目标方法不能是 private 或者 final 的.
SpringBoot 默认使用的是 CGLib 代理. 
综上, 如果要对我们项目中的某个方法进行 AOP 拦截通知, 那么这个方法不能是 private 或者 final 修饰的.

相关文章:

  • 自动化实现web端Google SignUp——selenium
  • 深入解析 Python 应用日志监控:ELK、Graylog 的实战指南
  • ​​​​​​​2025年第二十二届五一数学建模竞赛题目A题 支路车流量推测问题
  • 例数据中关键指标对应的SQL查询模板
  • 深度探索DeepSeek:从架构设计到性能优化的实战指南
  • 优雅关闭服务:深入理解 SIGINT / SIGTERM 信号处理机制
  • 装饰器模式深度解析:让对象功能扩展像乐高一样灵活 [特殊字符]
  • 0基础 | Proteus电路仿真 | 电机使用
  • 海量数据存储与分析:HBase vs ClickHouse vs Doris 三大数据库优劣对比指南
  • 《社交类应用开发:React Native与Flutter的抉择》
  • 【C语言】文本操作函数fseek、ftell、rewind
  • 装饰器设计模式(Decorator Pattern)详解
  • (A题|支路车流量推测问题)2025年第二十二届五一数学建模竞赛(五一杯/五一赛)解题思路|完整代码论文集合
  • Ubuntu18 登录界面死循环 Ubuntu进不了桌面
  • UN R79 关于车辆转向装置形式认证的统一规定(正文部分1)
  • 编程语言全景解析与编程技巧深度探索
  • 【算法扩展】斐波那契查找算法 - JAVA
  • Python结合QT进行开发
  • ES6函数、对象和面向对象扩展
  • QT6 源(66)篇三:阅读与注释类 QAbstractSpinBox ,这是螺旋框的基类,附上源码
  • 新势力4月销量出炉:零跑逾4万辆再夺冠,蔚来环比增近六成,小米下滑
  • 来论|受美国“保护”,日本民众要付出什么代价?
  • 深观察丨从“不建议将导师挂名为第一作者”说开去
  • “五一”假期逛上海车展请提前购票,展会现场不售当日票
  • 朝鲜新型驱逐舰“崔贤”号进行多项武器试验
  • 中国农业国际交流协会会长王守聪失联已逾半年,协会启动罢免