Springcloud学习笔记40--通过自定义注解实现AOP(面向切面编程)的运行时间计算(过滤器、拦截器、AOP)


1.AOP介绍

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

1.1 AOP通俗解释

我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。

按照正常的逻辑,我们可以这么做。

 这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。

 同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。

 1.2 AOP中的相关概念

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
  • Target(目标对象):织入 Advice 的目标对象.。
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

1.3 过滤器、拦截器、AOP功能区别

1.3.1 过滤器和拦截器的区别

从一个请求到返回数据的整个过程看,首先是请求进入Servlet,在Servlet中存在过滤器。对url进行拦截。后经过分发器后,会进入到spring容器,在请求尚未到达具体的controller时,会先经过拦截器的preHandle方法。方法通过后,进入业务层处理,返回实体,在进入postHandle方法。经过JSP后会进入afterCompletion方法,返回页面ModleAndView.

 1.3.2 AOP

面向切面拦截的是类的元数据(包、类、方法名、参数等),拦截是在controller层的类,接口,或者service层的类,方法的执行前后进行拦截。

相对于拦截器更加细致,而且非常灵活,拦截器只能针对URL做拦截,而AOP针对具体的代码,能够实现更加复杂的业务逻辑。

 1.3.3 过滤器、拦截器、AOP使用场景

三者功能类似,但各有优势,从过滤器–>拦截器–>切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。

比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能.

2.AOP实际使用场景

2.1 引入Pom依赖

<dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-aopartifactId>
dependency>

2.2 自定义注解

/**
 * @Author lucky
 * @Date 2022/1/26 15:33
 * 统计耗时
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExecuteTime {
}

2.3 AOP绑定自定义注解。并在aop中实现处理逻辑

package com.ttbank.flep.core.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;

/**
 * @Author lucky
 * @Date 2022/1/26 15:35
 */
@Aspect
@Component
@Slf4j
public class ExecuteTimeAspect {
    private static final ThreadLocal beginTimeThreadLocal = new NamedThreadLocal<>("ThreadLocal beginTime");

    //切点:决定用注解方式的方法切还是针对某个路径下的所有类和方法进行切,方法必须是返回void类型
    @Pointcut("@annotation(com.ttbank.flep.core.aspect.ExecuteTime)")
    private void logTimeCalculateCut(){

    }

    /**
     * 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
     * @param joinPoint 切点
     */
    @Before("logTimeCalculateCut()")
    public void doBefore(JoinPoint joinPoint) throws InterruptedException{
        beginTimeThreadLocal.set(System.currentTimeMillis());
    }


    /**
     * 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作
     */
    @AfterReturning("logTimeCalculateCut()")
    public void after(JoinPoint joinPoint){
        Long executeTime=System.currentTimeMillis()-beginTimeThreadLocal.get();
        log.info("执行时间为:"+executeTime);
    }

}

2.4 AOP的使用

用在接口层校验登陆的角色是否有权限使用该接口

@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
    @Autowired
    private IStudentService studentService;

    @PostMapping("/getStudentName")
    @ExecuteTime
    public void getStudentName(){
        log.info("studentName:lucky");
        Random random=new Random();
        int sleepTime = random.nextInt(10000);
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

postman测试:

控制台输出:

参考文献:

https://blog.csdn.net/zhanglf02/article/details/89787937

https://www.cnblogs.com/niceyoo/p/10907203.html