当前位置: 首页 > article >正文

Spring AOP的概念与使用

 (一)什么是AOP

   我们之前说spring的两个核心是 IOC,AOP,那IOC我们说了是控制反转那么AOP又是什么呢?

1.AOP的概念

   • Aspect Oriented Programming(⾯向切⾯编程)

  我们看到面向切面编程一定会感到熟悉,因为我们学习的java叫做面向对象编程。那唯一不同的就是面向的事物不同,所以这里我们就会有疑问,什么是切面呢?

  切面就是指某一类特定的问题,所以AOP又叫做面向特定方法编程(就比如spring中的拦截器和统一处理,就是AOP的应用),AOP是一种思想,上述的应用就是思想的实现

  总的来说:AOP是一种思想,是对某一类问题的统一处理

2.AOP思想的优点

   首先AOP是对一类问题的处理,所以他可以减少我们的重复代码,提高开发效率,并且方便我们日后对代码的更改

3.AOP的不同实现

  我们说AOP是一种思想,那思想就需要我们去实现,所以这么久以来就有很多实现了AOP的思想,就比如Spring AOP,也有AspectJ、CGLIB等. 那这里我们就来说一下Spring AOP.

  我们上文说了spring中给我们提供的拦截器,拦截器的作用维度是一次url请求和响应,但是AOP作用的维度就更加的细致,可以对更小的细节进行拦截,比如:包、类、⽅法 名、参数等(所以能实现更加复杂的业务逻辑)

  AOP应用的一个例子

 假设现在我们有一个项目,项目中有很多的业务功能,但是我们执行后发现,项目的执行效率比较低,耗时很长,这时我们就需要优化,但是优化前我们需要找到耗时最长的方法。这时我们可以记录这个方法运行前后的时间,来计算这个方法的耗时

  但是虽然这样可以计算出这个方法的耗时,但是如果我们每个方法都这样写不仅代码重复度提高,也会浪费我们很多时间,所以AOP这时就可以再不修改这些方法的基础上,针对特定的一类方法进行功能的增强

 AOP的作⽤:在程序运⾏期间在不修改源代码的基础上对已有⽅法进⾏增强(⽆侵⼊性:解耦)

(二)Spring AOP的入门

 1.引入依赖

我们需要在pom文件中添加配置

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

2.通过AOP来记录每个方法的执行时间

因为我们这里的方法就是个打印,所以这里耗时是0ms,其实是没有问题的

那接下来我们对这个代码进行一个简单的讲解

拆分:

  1. 注解

 @Aspect:声明了这是一个切面类

 @Around:环绕通知,再目标方法的前后都会执行(如果我们想的话,这个注解可以完成跟之后不同的通知类型一样的功能),后面跟着的表达式一会我们再说(暂时可以理解为对这些目录下的方法进行统一处理)

 其他的注解都是我们之前反复说的了,这里就不再多说了

 2. 参数和返回值

 joinpoint:就是相当于我们的方法,我们可以通过这个对象的proceed方法来执行我们的方法

 返回值:因为我们在这个统一处理中执行的我们的方法,我们的方法返回值已经被接收了,如果想要正常返回,就需要我们手动的去返回

整体

 如果我们整体来看这个代码,其实就分成了三个部分:

 1.方法执行前逻辑             2.原始方法执行          3.方法执行后逻辑

(三)Spring AOP详解

 1.Spring AOP的核心概念

      1)切点(Pointcut):切点的作用就是提供一组规则,来告诉程序对那种路径下的方法来进行统一处理

      2)连接点(Join Point):这个很好理解,满足切点提供的规则就是连接点

      3)通知(Advice):通知就是具体要做的工作,就是我们AOP的主体(也就是要如何统一处理)

      4)切面(Aspect) :就是切点+通知

汇总

2.通知类型

  我们刚刚说注解的时候说到了@Around注解 我们说这个注解是环绕通知,那除了环绕通知,还有什么通知类型?

 • @Around:环绕通知,此注解标注的通知⽅法在⽬标⽅法前,后都被执⾏

 • @Before:前置通知,此注解标注的通知⽅法在⽬标⽅法前被执⾏

 • @After:后置通知,此注解标注的通知⽅法在⽬标⽅法后被执⾏,⽆论是否有异常都会执⾏

 • @AfterReturning:返回后通知,此注解标注的通知⽅法在⽬标⽅法后被执⾏,有异常不会执⾏

 • @AfterThrowing:异常后通知,此注解标注的通知⽅法发⽣异常后执⾏

接下来我们写一些测试用例来试一下这些注解

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AspectDemo1 {
    @Before("execution(* com.example.demo.Controller.*.*(..))")
    public void doBefore(){
        log.info("doBefore");
    }
    @After("execution(* com.example.demo.Controller.*.*(..))")
    public void doAfter(){
        log.info("doAfter");
    }
    @Around("execution(* com.example.demo.Controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("AspectDemo do around before....");
        Object proceed = joinPoint.proceed();
        log.info("AspectDemo do around after....");
        return proceed;
    }
    @AfterThrowing("execution(* com.example.demo.Controller.*.*(..))")
    public void doAfterThrowing(){
        log.info("doAfterThrowing");
    }
    @AfterReturning("execution(* com.example.demo.Controller.*.*(..))")
    public void doAfterReturning(){
        log.info("doAfterReturning");
    }

}
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/time1")
    public String CalTime1(){
        log.info("计算时间Time1");
        return "计算时间Time1";
    }
    @RequestMapping("/time2")
    public String CalTime2(){
        log.info("执行");
        int a=10/0;
        return "CalTime2";
    }
}

那我们可以看到这些打印的日志

 我们发现确实如上面所说,如果我们的程序没有异常,那么 @AfterThrowing这个注解是不会执行的

  程序发⽣异常的情况下: • @AfterReturning 标识的通知⽅法不会执⾏, @AfterThrowing 标识的通知⽅法执⾏了 • @Around 环绕通知中原始⽅法调⽤时有异常,通知中的环绕后的代码逻辑也不会在执⾏了(因为 原始⽅法调⽤出异常了)

3.@PointCut

 上述代码我们会发现一个问题,我们的切点表达式,重复的次数太多了,我们可不可以把他给提出来?

 这时就需要我们Spring 提供的@PointCut注解,通过这个注解,我们可以提出公共的切点表达式,需要时再进行引入

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AspectDemo1 {
    @Pointcut("execution(* com.example.demo.Controller.*.*(..))")
    public void pt(){
    }
    @Before("pt()")
    public void doBefore(){
        log.info("doBefore");
    }
    @After("pt()")
    public void doAfter(){
        log.info("doAfter");
    }
    @Around("pt()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("AspectDemo do around before....");
        Object proceed = joinPoint.proceed();
        log.info("AspectDemo do around after....");
        return proceed;
    }
    @AfterThrowing("pt()")
    public void doAfterThrowing(){
        log.info("doAfterThrowing");
    }
    @AfterReturning("pt()")
    public void doAfterReturning(){
        log.info("doAfterReturning");
    }
}

这时我们的代码就变成了这个样子

  而且这个切点的定义不仅仅可以再当前的切面类中使用我们在其他切面类也可以使用(但是注意我们如果使用private就只能在当前切面类中使用)  

4.切面优先级@Order

  我们再来看一下之前打印的日志

  我们发现这个有点类似于括号{},两者的位置都是对应的,这就会让我们想到这个执行顺序是什么样的,因为位置对应,所以大概率不是随机的

 所以我们需要通过程序来求证一下

我们发现存在多个切面类时,默认按照切面类名字母排序:

• @Before 通知:字⺟排名靠前的先执⾏

• @After 通知:字⺟排名靠前的后执⾏

  但是这种方式我们不可能每次打印都要考虑类名,这个会给我们增添不必要的麻烦,有没有一种方式,可以告知我们Spring 我们这些切面通知的执行顺序? 这时就需要@Order这个注解

 我们通过这个注解可以指定优先级,先执行优先级高的切面,然后执行优先级低的切面

5.切点表达式

 我们常见的切点表达式有两种,一种是我们刚刚使用的excution(根据方法的签名来匹配),一种时需要我们使用自定义注解的@annotation(根据注解来匹配)

   1)excution表达式

  我们把注解+excution表达式叫做切点,那我们来拆分看看这个切点都有什么

  首先这里频繁出现的..和*我们来看一下是什么意思,其实很简单之前我们也接触了不少,*大多数都代表任意的意思,就比如我们mysql中的select * 查询所有列,那这里的..代表匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数

这就是一个不省略的完成切点表达式

注意:我们在参数前,除了匹配特定的一个方法,其他省略了路径也要加*(就比如这样)

2)@annotation(自定义注解)

   excution表达式中作用的路径都是有规则的,那如果我们要匹配多个无规则的方法呢?

   这时我们要使用excution切点表达式就要写多个,描述起来也不是很方便,这时候我们就可以使用自定义注解的方式和另一种切点表达式@annotation来描述

  自定义注解:

       首先我们要创建一个注解类,在创建前我们看看spring中的注解都有那些信息(这个之前我们也说过)

我们也可以多点开几个看一下,我们会发现@Target和@Retention每个注解中都有

  那我们来看一下这两个注解有什么用

1.@Target 表示了这个注解修饰的对象是社么,是类还是方法?

常用取值

ElementType.TYPE:⽤于描述类、接⼝(包括注解类型)或enum声明

ElementType.METHOD:描述⽅法 

ElementType.PARAMETER:描述参数

ElementType.TYPE_USE:可以标注任意类型

还有一些其他值,我们可以去看源码,这里因为太长了就不展示了

2.@Retention表示了这个注解的生命周期

有三种生命周期,分别是

source:注解仅存在于源代码中,编译成字节码后就会被丢弃

class:注解存在于源代码和字节码中,运行时丢弃

runtime:注解存在于源代码,字节码和运⾏时中

   然后我们要添加一个切面类,使用@annotation切点表达式定义切点,并且只对@MyAspect生效

之后我们只需要在对应方法上添加@MyAspect注解就可以了

这样我们的自定义注解的创建和使用就完成了

常见面试题:

  Spring AOP的实现方法有那些

 1.基于注解的方式@Aspect

 2.基于自定义注解

 3.基于Spring API(通过配置xml方式)

 4.基于代理来实现


http://www.kler.cn/news/363950.html

相关文章:

  • 链路分析对性能测试的意义
  • 消息会话—发送消息自动滚动到最底部
  • 关于写“查看IT设备详细信息”接口的理解
  • 如何禁止上班期间浏览无关网站?
  • GraphLLM:基于图的框架,通过大型语言模型处理数据
  • Scala的trait特质
  • 构建后端为etcd的CoreDNS的容器集群(一)、生成自签名证书
  • java的maven打包插件来了,package一键打包exe、dmg、rpm等
  • 小程序开发语言Java跟php的区别
  • Element Plus的el-tree-v2 组件实现仅叶子节点显示勾选框,并且只能单选
  • MYSQL-SQL-04-DCL(Data Control Language,数据控制语言)
  • 若依框架vue3模板
  • 单例模式是一种常见的设计模式,确保一个类只有一个实例,并提供一个全局访问点。
  • Linux Redis查询key与移除日常操作
  • 尚硅谷redis 第97节 redisTmplate下答疑
  • 代码随想录算法训练营第二天| 209.长度最小的子数组 59.螺旋矩阵II 区间和 开发商购买土地
  • 身份证识别JAVA+OPENCV+OCR
  • ref属性的作用对象类型
  • 文件操作(1) —— 文件基础知识
  • 【C++】——list 容器的解析与极致实现
  • 修改IDEA中@author变量user内容
  • 开源软件搜索工具:Reddo
  • React是如何处理事件的?
  • linux 将已经启动的java应用的控制台日志输出出来, 不停应用的情况下
  • Java 使用 itextpdf 自定义 生成 pdf
  • CSS 网格布局