前言

前面的项目啥的都完成得差不多,写这篇文章主要是为了复习,针对面试问题来进行复习总结。

先大致了解一下什么是AOP?

AOP称为面向切面编程, 用于将那些与业务无关的, 却对多个对象产生影响的公共行为和逻辑, 抽取并封装为一个可重用的模块, 这个模块被命名为 " 切面 ", 减少系统中的重复代码, 减低了模块间的耦合度, 同时提高了系统的可维护性.

常见的AOP使用场景:

  • 记录操作日志
  • 缓存处理
  • Spring中内置的事务处理

AOP快速入门:

需求:统计部门管理各个业务层方法执行耗时。

原始方法 : 就是在业务执行之前记录一下开始时间, 执行之后记录结束时间, 最后相减得出执行耗时.

在这里插入图片描述
我们可以发现有好多重复的代码, 这里就可以用AOP来实现, 只需要一小段代码.

在这里插入图片描述
1). 导入依赖:在 pom.xml 文件中导入 AOP 的依赖

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

2). 编写AOP程序:针对于特定方法根据业务需要进行编程

@Component
@Aspect //当前类为切面类
@Slf4j
public class RecordTimeAspect {

    @Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        //记录方法执行开始时间
        long begin = System.currentTimeMillis();

        //执行原始方法
        Object result = pjp.proceed();

        //记录方法执行结束时间
        long end = System.currentTimeMillis();

        //计算方法执行耗时
        log.info("方法执行耗时: {}毫秒",end-begin);
        return result;
    }
}

AOP核心构成

简单来说: AOP 的核心构成包括切面(封装横切逻辑)、切点(定义拦截位置)、通知(指定执行动作)、连接点(可拦截的程序执行点)以及织入(将切面融入目标对象的过程)。

拿入门案例来举例:
在这里插入图片描述
下面来详细的看一下各个组成部分:

通知:Advice

1. 什么是通知?

指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)

  • 在入门程序中是需要统计各个业务方法的执行耗时的,此时我们就需要在这些业务方法运行开始之前,先记录这个方法运行的开始时间,在每一个业务方法运行结束的时候,再来记录这个方法运行的结束时间。
  • 是在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。

在这里插入图片描述

常见的通知类型:

常见的AOP操作呢,就是把我们要执行的业务逻辑(目标方法抽离出来再加上一些方法) , 就比如入门案例中在我们的目标方法 ( Object result = pjp.proceed(); ) 前后都加上了方法记录执行时间 , 这就叫做环绕通知 , 但有些不需要在目标方法前后都执行方法, 这就涉及到不同的通知类型. ( 就比如常见的公共字段填充方法, 他就只需要在mapper层执行数据库语句前插入公共字段就可以, 他的通知类型就是前置通知(@Before) )

只要我们在通知方法上加上注解,就可以代表当前的通知类型了。( 比如我们在通知方法上加上了@Around注解,就代表当前通知是一个环绕通知。 )

@Around环绕通知,此注解标注的通知方法在目标方法前、后都被执行
@Before前置通知,此注解标注的通知方法在目标方法前被执行
@After后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
@AfterReturning返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing异常后通知,此注解标注的通知方法发生异常后执行
@Slf4j
@Component
@Aspect
public class MyAspect1 {
    //前置通知
    @Before("execution(* com.itheima.service.*.*(..))")
    public void before(JoinPoint joinPoint){
        log.info("before ...");

    }

    //环绕通知
    @Around("execution(* com.itheima.service.*.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();
        
        //原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了
        
        log.info("around after ...");
        return result;
    }

    //后置通知
    @After("execution(* com.itheima.service.*.*(..))")
    public void after(JoinPoint joinPoint){
        log.info("after ...");
    }

    //返回后通知(程序在正常执行的情况下,会执行的后置通知)
    @AfterReturning("execution(* com.itheima.service.*.*(..))")
    public void afterReturning(JoinPoint joinPoint){
        log.info("afterReturning ...");
    }

    //异常通知(程序在出现异常的情况下,执行的后置通知)
    @AfterThrowing("execution(* com.itheima.service.*.*(..))")
    public void afterThrowing(JoinPoint joinPoint){
        log.info("afterThrowing ...");
    }
}

🧩扩展@PointCut注解:

我们发现啊,每一个注解里面都指定了切入点表达式(“execution(* com.itheima.service..(…))”),而且这些切入点表达式都一模一样。此时我们的代码当中就存在了大量的重复性的切入点表达式,假如此时切入点表达式需要变动,就需要将所有的切入点表达式一个一个的来改动,就变得非常繁琐了。

可以使用@PointCut注解,该注解的作用是将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可。

@Slf4j
@Component
@Aspect
public class MyAspect1 {

    //切入点方法(公共的切入点表达式)
    @Pointcut("execution(* com.itheima.service.*.*(..))")
    private void pt(){}

    //前置通知(引用切入点)
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info("before ...");

    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();
        //原始方法在执行时:发生异常
        //后续代码不在执行

        log.info("around after ...");
        return result;
    }

    //后置通知
    @After("pt()")
    public void after(JoinPoint joinPoint){
        log.info("after ...");
    }

    //返回后通知(程序在正常执行的情况下,会执行的后置通知)
    @AfterReturning("pt()")
    public void afterReturning(JoinPoint joinPoint){
        log.info("afterReturning ...");
    }

    //异常通知(程序在出现异常的情况下,执行的后置通知)
    @AfterThrowing("pt()")
    public void afterThrowing(JoinPoint joinPoint){
        log.info("afterThrowing ...");
    }
}

切入点:PointCut

匹配连接点的条件,通知仅会在切入点方法执行时被应用。

  • 在通知当中,我们所定义的共性功能到底要应用在哪些方法上?此时就涉及到了切入点pointcut概念。切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。
  • 在aop的开发当中,我们通常会通过一个切入点表达式来描述切入点

在这里插入图片描述

切入点表达式

首先通过这个入门案例来讲讲这个切入点表达式要注意的点

     @Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
部分含义
execution(…)表示匹配方法执行的连接点(Spring AOP 只支持方法级别的连接点)
com.itheima.service.impl.DeptServiceImpl包路径 com.itheima.service.impl.DeptServiceImpl 是固定包名
*(第一个)返回类型:* 表示任意返回类型(包括 void)
.*方法名:* 表示任意方法名
(…)参数列表:… 表示任意类型、任意数量的参数(包括无参)
  • ’ * ':单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
  • ’ … ’ :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

🙌🙌🙌切入点表达式:描述切入点方法的一种表达式

  • 作用:主要用来决定项目中的哪些方法需要加入通知
  • 常见形式:
      1. execution(……):根据方法的签名来匹配
      1. @annotation(……) :根据注解匹配
execution

execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

其中带?的表示可以省略的部分

  • 访问修饰符:可省略(比如: public、protected)
  • 包名.类名: 可省略
  • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

切入点表达式的语法规则:

  1. 方法的访问修饰符可以省略
  2. 返回值可以使用*号代替(任意返回值类型)
  3. 包名可以使用 * 号代替,代表任意包(一层包使用一个*)
  4. 使用…配置包名,标识此包以及此包下的所有子包
  5. 类名可以使用*号代替,标识任意类
  6. 方法名可以使用*号代替,表示任意方法
  7. 可以使用 * 配置参数,一个任意类型的参数
  8. 可以使用… 配置参数,任意个任意类型的参数

切面:Aspect,描述通知与切入点的对应关系(通知+切入点)

当通知和切入点结合在一起,就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法,在什么时候执行什么样的操作。

在这里插入图片描述
小白啊!!!写的不好轻喷啊🤯如果觉得写的不好,点个赞吧🤪(批评是我写作的动力)

…。。。。。。。。。。。…

…。。。。。。。。。。。…请添加图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐