当前位置: 首页> 教育> 锐评 > Spring AOP

Spring AOP

时间:2025/7/10 15:24:41来源:https://blog.csdn.net/m0_60963435/article/details/140930836 浏览次数:0次

文章目录

  • Spring AOP
    • 1. 理解AOP
    • 使用Spring AOP
    • 2. Spring AOP详解
      • 2.1 Spring AOP中涉及的几个核心概念
        • 切点(Pointcut)
        • 连接点(Join Point)
        • 通知(Advice)
        • 切面
      • 2.2 通知类型
      • 2.2 `@PointCut`
      • 2.3 切面优先级`@Order`
      • 2.4 切点表达式
        • `execution`
        • `@annotation`


Spring AOP

1. 理解AOP

AOP即面向切面编程(Aspect Oriented Programming)

什么是面向切面编程?
image.png
切面指的是某一类特定的问题,所以AOP也可以理解为面向特定方法编程
比如对于网页登录,后端实现的登录拦截,就是一类特点问题
其中,登录拦截器就是对"登录校验"这类问题的统一处理
拦截器就是AOP思想的一种应用
同样的,统一数据返回格式和统一异常处理,也是AOP思想的实现
总的来说,AOP是一种思想,是对某一类问题的集中处理

举个例子:
image.png
假设这个项目的方法调用链比较复杂,现在有一些业务的执行效率很低,需要我们对耗时比较长的接口进行优化
首先就是确定出执行时间比较长的方法是哪些,就需要去统计每一个业务方法的执行耗时
可以在业务方法的执行前后记录时间,从而算出耗时
但是如果简单粗暴的去给每个方法添加计时代码,不是一件容易的工作
image.png

AOP就可以实现在不改动这些代码的基础上,针对特点的方法就进行功能的增强,即无侵入性
image.png

使用Spring AOP

我们针对图书管理系统的Controller进行耗时计算:
首先要AOP依赖

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

编写程序

@Slf4j
@Aspect
@Component
public class TimeRecordAspect {@Around("execution(* org.jwcb.book.demo.controller.*.*(..))")public Object timeRecord(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();Object result = joinPoint.proceed();long end = System.currentTimeMillis();log.info(joinPoint.getSignature() + "耗时" + (end - start) + "ms");return result;}
}

执行后依次访问每个接口:
image.png
通过上面的例子,我们也能简单的感受到AOP的几个优势:

  1. 代码无侵入
  2. 减少了重复代码
  3. 提高了开发效率
  4. 维护方便

image.png

2. Spring AOP详解

2.1 Spring AOP中涉及的几个核心概念

切点(Pointcut)

Pointcut的作用就是提供一组规则,告诉程序对哪些方法来进行功能加强
image.png
这里的表达式execution(* org.jwcb.book.demo.controller.*.*(..))就是切点表达式

连接点(Join Point)

满足上一点所说的切点表达式规则的方法,就是切点
image.png

通知(Advice)

通知就是具体要做工作,指那些重复,共性的工作(最终体现为一个方法)
image.png

切面

切面 = 切点 + 通知
通过切面就能够知道当前AOP程序需要针对哪些方法,在什么时候执行什么样的操作
image.png
切面所在的类,称为切面类(被@Aspect标注的类)

2.2 通知类型

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

  • @Around环绕通知,此注解标注的通知方法在目标方法前后都被执行
  • @Before前置通知,此注解标注的通知方法在目标方法前被执行
  • @After后置通知,此标注的通知方法在目标方法后被执行,且无论是否出现异常都会执行
  • @AfterReturning返回后通知,此注解标注的方法在目标方法执行后被执行,有异常不会执行
  • @AfterThrowing异常后通知,此注解标注的方法在发生异常后执行
@RequestMapping("/test")
@RestController
public class TestController {@RequestMapping("Test1")public String Test1(){return "t1";}@RequestMapping("/Test2")public String Test2() {int a = 10 / 0;return "t2";}
}@Slf4j
@Component
@Aspect
public class TestAspect {@Before("execution(* org.jwcb.je0803.controller.*.*(..))")public void doBefore() {log.info("执⾏ Before ⽅法");}@After("execution(* org.jwcb.je0803.controller.*.*(..))")public void doAfter() {log.info("执⾏ After ⽅法");}@AfterReturning("execution(* org.jwcb.je0803.controller.*.*(..))")public void doAfterReturning() {log.info("执⾏ AfterReturning ⽅法");}@AfterThrowing("execution(* org.jwcb.je0803.controller.*.*(..))")public void doAfterThrowing() {log.info("执⾏ doAfterThrowing ⽅法");}@Around("execution(* org.jwcb.je0803.controller.*.*(..))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around ⽅法开始执⾏");Object result = joinPoint.proceed();log.info("Around ⽅法结束执⾏");return result;}
}

正常访问的情况:
image.png
此时@AfterThrowing 标识的通知不会执行
同时,@Around标识的通知方法包含前后两部分,前置的在@Before标识的通知方法前执行,后置的在@After标识的方法后执行

抛出异常情况:
image.png
此时@AfterReturning 标识的通知⽅法不会执行, @AfterThrowing 标识的通知⽅法执行了
并且@Around 环绕通知中原始方法法调用时有异常,通知中的环绕后的代码逻辑也不会在执行了
image.png

2.2 @PointCut

Spring提供了@PointCut注解,将公共的切点表达式提取出来,需要时引用使用即可

@Slf4j
@Component
@Aspect
public class TestAspect {@Pointcut("execution(* org.jwcb.je0803.controller.*.*(..))")public void pointcut() {}@Before("pointcut()")public void doBefore() {log.info("执⾏ Before ⽅法");}@After("pointcut()")public void doAfter() {log.info("执⾏ After ⽅法");}@AfterReturning("pointcut()")public void doAfterReturning() {log.info("执⾏ AfterReturning ⽅法");}@AfterThrowing("pointcut()")public void doAfterThrowing() {log.info("执⾏ doAfterThrowing ⽅法");}@Around("pointcut()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around ⽅法开始执⾏");Object result = joinPoint.proceed();log.info("Around ⽅法结束执⾏");return result;}
}

2.3 切面优先级@Order

有时候我们在一个项目里面定义了多个切面类的时候,如果这些切面类的多个切点都匹配了同一个方法,那么执行的时候,顺序是什么样的??
测试一下:
新建类TestAspect1,TestAspect2,TestAspect3,内容与TestAspect上面的一样

@Slf4j
@Component
@Aspect
public class TestAspect3 {@Pointcut("execution(* org.jwcb.je0803.controller.*.*(..))")public void pointcut() {}@Before("pointcut()")public void doBefore() {log.info("执⾏ Before ⽅法");}@After("pointcut()")public void doAfter() {log.info("执⾏ After ⽅法");}@AfterReturning("pointcut()")public void doAfterReturning() {log.info("执⾏ AfterReturning ⽅法");}@AfterThrowing("pointcut()")public void doAfterThrowing() {log.info("执⾏ doAfterThrowing ⽅法");}@Around("pointcut()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around ⽅法开始执⾏");Object result = joinPoint.proceed();log.info("Around ⽅法结束执⾏");return result;}
}

image.png
根据测试结果,可以知道,当存在多个切面类的时候,默认是按照类名字母的顺序排序
总体特点有点像栈先进后出的
但是,这种方式不太适合管理
image.png
Spring提供了一个注解@Order,可以用来控制通知的执行顺序
使用方式:
image.png
执行结果:
image.png
可以看出,根据我们定义的优先级,数组越小的先执行
image.png

2.4 切点表达式

execution

我们上面使用的execution()是最常用的切点表达式,语法为:
image.png

  1. * 表示匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)
    1. 包名使用*表示任意包(一层包用一个*)
    2. 类名使用*表示任意类
    3. 返回值使用*表示任意返回类型
    4. 方法名使用*表示任意方法
    5. 参数使用*表示一个任意类型的参数
  2. ..表示匹配多个连续的任意符号,可以调配任意层级的包,或任意类型,任意个数的参数
    1. 使用..配置包名,表示次包下的所有子包
    2. 使用..配置方法参数,表示任意个任意类型的参数

注意:访问修饰符和异常是可以省略的

@annotation

execution适用于有规则的,当时当我们要匹配多个之间无规则的方法,使用execution就不方便了
我们可以借助自定义注解的方式以及另一种切点表达式@annotation来描述这一类的切点
自定义注解

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

@Target表示这个注解可以用在什么地方

  • TYPE用于描述类,接口(包括注解类型)或enum
  • METHOD:描述方法
  • PARAMETER:描述参数
  • TYPE_USE:可以标注任意类型

@Retention指注解被保留的时间长短,标明注解的生命周期

  • SOURCE:表示注解仅存在于源代码中,编译成字节码之后会被丢弃
  • CLASS:编译时注解,表示注解存在于源代码和字节码中,但是运行时会被丢弃
  • RUNTIME:运行时注解,表示注解存在于源代码,字节码和运行时中

定义切面类
使用@annotation切点表达式切点

@Slf4j
@Aspect
@Component
public class CostTimeAspect {@Around("@annotation(org.jwcb.je0803.aspect.TimeCost)")public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();Object result = joinPoint.proceed();long end = System.currentTimeMillis();log.info(joinPoint.getSignature().toLongString()+"cost time: {} ms", end - start);return result;}
}

这样的话,如果我们需要计算某一个方法的执行耗时,只需要将自定义的注解加在方法上面即可

@RequestMapping("/test")
@RestController
public class TestController {@TimeCost@RequestMapping("Test1")public String Test1(){return "t1";}}

访问:
image.png

关键字:Spring AOP

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: