当前位置: 首页 > 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 修饰的.

http://www.xdnf.cn/news/248113.html

相关文章:

  • pycharm安装的插件怎么显示在右侧
  • 【无标题】四色拓扑收缩模型中环形套嵌结构的颜色保真确定方法
  • 【信息系统项目管理师-论文真题】2024上半年(第一批)论文详解(包括解题思路和写作要点)
  • C++11新特性_自动类型推导_decltype
  • Java内存对象实现聚合查询
  • Unity SpriteMask(精灵遮罩)
  • PMP-第八章 项目质量管理
  • 攻防世界 dice_game
  • 多智能体空域协同中的伦理博弈与系统调停
  • LegalOne:本土与国际视野融合的法律评级,大湾区律师及律师事务所榜单申报启动
  • 【统计方法】方差分析(ANOVA):判断数据差异的统计方法
  • 【Linux】环境基础开发工具使用
  • 26.电流信号的强抗干扰能力运用
  • 深圳第三方软件测试机构如何填补企业空缺并助力市场发展?
  • LintCode第652题-递归版
  • Linux基础指令【下】
  • Leetcode刷题报告2——双指针法
  • 基于DrissionPage的高效爬虫开发:以小说网站数据抓取为例
  • vue自定义表头内容excel表格导出
  • LangChain4j +DeepSeek大模型应用开发——7 项目实战 创建硅谷小鹿
  • SpringAI使用OpenAI API格式调用DeepSeek服务
  • 《AIStarter安装部署全攻略:AI绘画/数字人项目快速上手指南(含Windows环境配置要点)》
  • *(解引用运算符)与 ++(自增运算符)的优先级
  • 开始一个vue项目
  • 《排序算法总结》
  • 60常用控件_QSpinBox的使用
  • [FPGA Video IP] Frame Buffer Read and Write
  • 一文读懂EMC VNX存储的Fast Cache(第二部分:对比)
  • 【RocketMQ】- 源码系列目录
  • 实习入职的总结