AOP基础

374 阅读4分钟

AOP简介

  • AOP(Aspect Oriented Programming) ‌:面向切面编程,可简单理解为面向特定方法编程。
  • 场景‌:部分业务方法运行较慢,需要统计每一个业务方法的执行耗时。

AOP优势

  • 减少重复代码‌:通过切面统一处理横切关注点,减少业务代码中的重复逻辑。
  • 代码无侵入‌:切面代码与业务代码分离,业务代码无需关心横切关注点的实现。
  • 提高开发效率‌:快速实现横切关注点,如日志、事务管理等。
  • 维护方便‌:横切关注点集中管理,便于维护和升级。

Spring AOP开发步骤

  1. 引入AOP依赖‌:在pom.xml中引入spring-boot-starter-aop依赖。
  2. 编写AOP程序‌:定义切面类,使用@Aspect注解,编写通知方法,并使用切入点表达式匹配目标方法。

AOP核心概念

  • 连接点(JoinPoint) ‌:可以被AOP控制的方法,暗含方法执行时的相关信息。
  • 通知(Advice) ‌:指那些重复的逻辑,即共性功能,最终体现为一个方法。
  • 切入点(PointCut) ‌:匹配连接点的条件,通知仅会在切入点方法执行时被应用。
  • 切面(Aspect) ‌:描述通知与切入点的对应关系(通知+切入点)。
  • 目标对象(Target) ‌:通知所应用的对象。

AOP进阶

通知类型

  • @Around‌:环绕通知,在目标方法前、后都被执行,需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行。
  • @Before‌:前置通知,在目标方法前被执行。
  • @After‌:后置通知,在目标方法后被执行,无论是否有异常都会执行。
  • @AfterReturning‌:返回后通知,在目标方法返回后被执行,有异常不会执行。
  • @AfterThrowing‌:异常后通知,在目标方法发生异常后执行。

切入点表达式

  • execution‌:根据方法的签名来匹配,语法为execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)

    • *:单个独立的任意符号。
    • ..:多个连续的任意符号。
  • @annotation‌:根据注解匹配,用于匹配标识有特定注解的方法。

通知顺序

  • 当有多个切面的切入点都匹配到了目标方法时,多个通知方法都会被执行。

  • 执行顺序:

    • 不同切面类中,默认按照切面类的类名字母排序。
    • 目标方法前的通知方法:字母排名靠前的先执行。
    • 目标方法后的通知方法:字母排名靠前的后执行。
  • 可以使用@Order(数字)加在切面类上来控制顺序,数字小的先执行(目标方法前)或后执行(目标方法后)。

连接点信息

  • 在Spring中用JoinPoint抽象了连接点,可以获取方法执行时的相关信息,如目标类名、方法名、方法参数等。
  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint
  • 对于其他四种通知,获取连接点信息只能使用JoinPoint

ThreadLocal应用

  • ThreadLocal‌:线程的局部变量,为每个线程提供单独一份存储空间,具有线程隔离的效果。

  • 应用场景‌:在同一个线程/同一个请求中,进行数据共享,如存储当前登录员工ID。

  • 操作步骤‌:

    1. 定义ThreadLocal操作的工具类。
    2. TokenFilter中解析完当前登录员工ID,将其存入ThreadLocal
    3. 在AOP程序中,从ThreadLocal中获取当前登录员工的ID。

AOP案例

案例需求

  • 将案例中增、删、改相关接口的操作日志记录到数据库表中。
  • 日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长。

实现步骤

  1. 定义日志实体类和Mapper接口‌:用于存储操作日志信息。
  2. 定义切面类‌:使用@Around环绕通知,匹配增、删、改接口方法。
  3. 在切面类中获取当前登录员工ID‌:通过ThreadLocal获取。
  4. 记录日志信息‌:在切面类中编写逻辑,将日志信息保存到数据库。

切入点表达式

  • 使用execution表达式匹配增、删、改接口方法,如:
   javaCopy Code
   @Around("execution(* com.itheima.controller.*.save(..)) || " +
           "execution(* com.itheima.controller.*.delete(..)) || " +
           "execution(* com.itheima.controller.*.update(..))")
   ```

-   或者使用自定义注解`@Log`,通过`@annotation`表达式匹配:

   ```
   javaCopy Code
   @Around("@annotation(com.itheima.anno.Log)")
   ```