详解Spring AOP(一)

目录

1. AOP概述

2.Spring AOP快速入门

2.1引入AOP依赖

2.2编写AOP程序

 3.Spring AOP核心概念

3.1切点(PointCut)

3.2连接点(Join Point)

3.3通知(Advice)

3.4切面(Aspect)

4.通知类型

 5.@PointCut

 6.切面优先级 @Order


1. AOP概述

AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,它旨在通过预编译方式和运行期间动态代理实现程序功能的统一维护。

AOP定义

  1. AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点技术。
  2. 它是Spring框架中的一个重要内容,也是函数式编程的一种衍生范型。
  3. AOP通过“切面”对业务逻辑的各个部分进行隔离,降低业务逻辑之间的耦合度,提高程序的可重用性和开发效率。

 与OOP的区别:

OOP针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。

AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

应用场景:记录日志、性能监控、权限控制、缓存优化、事务管理(如声明式事务)

案例分析:

我们现在有⼀个项⽬ , 项⽬中开发了很多的业务功能

现在有⼀些业务的执行效率比较低, 耗时较长, 我们需要对接口进行优化.

第⼀步就需要定位出执行耗时比较长的业务方法, 再针对该业务方法来进行优化

何定位呢? 我们就需要统计当前项目中每⼀个业务方法的执行耗时.

如何统计呢? 可以在业务方法运行前和运行后, 记录下方法的开始时间和结束时间, 两者之差就是这个方法的耗时.

这种方法是可以解决问题的, 但⼀个项⽬中会包含很多业务模块, 每个业务模块又有很多接口 , ⼀个接口又包含很多方法, 如果我们要在每个业务方法中都记录方法的耗时, 会增加特别多的工作.

AOP就可以做到在不改动这些原始方法的基础上, 针对特定的方法进⾏功能的增强.

AOP的作用:在程序运行期间在不修改源代码的基础对已有方法进行增强(无侵⼊性: 解耦)

2.Spring AOP快速入门

需求:统计系统各个方法的zhixing

2.1引入AOP依赖

pom.xml文件中添加配置

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

2.2编写AOP程序

记录Controller中每个方法的执行时间

 @Slf4j
 @Aspect
 @Component
 public class TimeAspect {
     /**
      * 记录⽅法耗时 
     */
     @Around("execution(* com.example.demo.controller.*.*(..))")
     public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { 
        //记录⽅法执⾏开始时间
        long begin = System.currentTimeMillis(); 
        //执⾏原始⽅法
        Object result = pjp.proceed(); 
        //记录⽅法执⾏结束时间
        long end = System.currentTimeMillis(); 
        //记录⽅法执⾏耗时
         log.info(pjp.getSignature() + "执⾏耗时 : {}ms", end - begin);
         return result;
     }
 }

运行程序, 观察日志

 对代码进行简单分析:

1.@Aspect:标识这是一个切面类
2.@Around:环绕通知,在目标方法的前后都会被执行.后面的表达式表示对哪些方法进行增强
3.ProceedingJoinPoint.proceed()让原始方法执行

整个代码划分为三部分 

我们通过AOP入门程序完成了业务接口执行耗时的统计.

通过上面的程序,我们也可以感受到AOP面向切面编程的一些优势:

  • 代码无侵入:不修改原始的业务方法,就可以对其进行了功能的增强或者是功能的改变
  • 减少了重复代码
  • 提高开发效率
  • 维护方便

 3.Spring AOP核心概念

3.1切点(PointCut)

切点(Pointcut),也称之为"切入点"
切点的作用就是提供一组规则(使用AspectJ pointcut expression language来描述),告诉程序对哪些方法来进行功能增强.

上面的表达式execution(* com.example.demo.controller.*.*(..))就是切点表达式. 

3.2连接点(Join Point)

满足切点表达式规则的方法,就是连接点.也就是可以被AOP控制的方法以入门程序举例,所有com.example.demo.controller路径下的方法,都是连接点.

 package com.example.demo.controller; 

 @RequestMapping("/book") 
 @RestController
 public class BookController { 

     @RequestMapping("/addBook")
     public Result addBook(BookInfo bookInfo) {
        //...代码省略
     } 

     @RequestMapping("/queryBookById")
     public BookInfo queryBookById(Integer bookId){
        //...代码省略
     } 

     @RequestMapping("/updateBook")
     public Result updateBook(BookInfo bookInfo) {
        //...代码省略
     }
 }

上述BookController中的方法都是连接点

切点和连接点的关系

连接点是满足切点表达式的元素.切点可以看做是保存了众多连接点的一个集合

比如:

切点表达式:学校全体教师

连接点就是:张三,李四等各个老师

3.3通知(Advice)

通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)比如上述程序中记录业务方法的耗时时间,就是通知.

在AOP面向切面编程当中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容.

3.4切面(Aspect)

切面(Aspect)=切点(Pointcut)+通知(Advice)

通过切面就能够描述当前AOP程序需要针对于哪些方法,在什么时候执行什么样的操作.

切面既包含了通知逻辑的定义,也包括了连接点的定义.

 切面所在的类,我们一般称为切面类(被@Aspect注解标识的类)

4.通知类型

上面我们讲了什么是通知,接下来学习通知的类型.

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

@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
 @Aspect
 @Component
 public class AspectDemo { 
     //前置通知
     @Before("execution(* com.example.demo.controller.*.*(..))")
     public void doBefore() {
        log.info("执⾏  Before ⽅法");
     } 

     //后置通知
     @After("execution(* com.example.demo.controller.*.*(..))")
     public void doAfter() {
        log.info("执⾏  After ⽅法");
     } 

     //返回后通知
     @AfterReturning("execution(* com.example.demo.controller.*.*(..))")
     public void doAfterReturning() {
        log.info("执⾏  AfterReturning ⽅法");
     } 

     //抛出异常后通知
     @AfterThrowing("execution(* com.example.demo.controller.*.*(..))")
     public void doAfterThrowing() {
        log.info("执⾏  doAfterThrowing ⽅法");
     } 

     //添加环绕通知
     @Around("execution(* com.example.demo.controller.*.*(..))")
     public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
         log.info("Around ⽅法开始执⾏");
        Object result = joinPoint.proceed();
         log.info("Around ⽅法结束执⾏");
         return result;
     }
 }

编写测试程序:

 package com.example.demo.controller; 

 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;

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

     @RequestMapping("/t2")
     public boolean t2() {
          int a = 10 / 0;
         return true;
    }
 }

运行程序,观察日志:
1.正常运行的情况

http://127.0.0.1:8080/test/t1

观察日志

程序正常运行的情况下,@AfterThrowing标识的通知方法不会执行
从上图可以看出,@Around标识的通知方法包含两部分,一个"前置逻辑",一个"后置逻辑",其中"前置逻辑"会先于@Before标识的通知方法执行,"后置逻辑"会晚于@After标识的通知方法执行

2.异常时的情况

http://127.0.0.1:8080/test/t2

观察日志: 

程序发生异常的情况下:
@AfterReturning标识的通知方法不会执行@AfterThrowing标识的通知方法执行了
@Around环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会在执行

注意事项:
@Around环绕通知需要调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行.
@Around环绕通知方法的返回值,必须指定为Object来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的.
一个切面类可以有多个切点.

 5.@PointCut


上面代码存在一个问题,就是存在大量重复的切点表达式execution(* com.example.demo.controller.*.*(..)),

Spring提供@PointCut注解,把公共的切点表达式提取出来,需要用到时引用该切入点表达式即可.
上述代码就可以修改为:

 @Slf4j
 @Aspect
 @Component
 public class AspectDemo {
     //定义切点(公共的切点表达式)
     @Pointcut("execution(* com.example.demo.controller.*.*(..))")
     private void pt(){}
     //前置通知
     @Before("pt()")
     public void doBefore() {
        //...代码省略
     } 

     //后置通知
     @After("pt()")
     public void doAfter() {
        //...代码省略
     } 

     //返回后通知
     @AfterReturning("pt()")
     public void doAfterReturning() {
        //...代码省略
     } 

     //抛出异常后通知
     @AfterThrowing("pt()")
     public void doAfterThrowing() {
        //...代码省略
     } 

     //添加环绕通知     
     @Around("pt()")
     public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //...代码省略
     }
 }

注意:

当切点定义使用private修饰时,仅能在当前切面类中使用,当其他切面类也要使用当前切点定义时,就需要把private改为public.

引用方式为:全限定类名.方法名()

 @Slf4j
 @Aspect
 @Component
 public class AspectDemo2 { 
     //前置通知
     @Before("com.example.demo.aspect.AspectDemo.pt()")
     public void doBefore() {
        log.info("执⾏  AspectDemo2 -> Before ⽅法");
     }
 }

 6.切面优先级 @Order

当我们在一个项目中,定义了多个切面类并且这些切面类的多个切入点都匹配到了同一个目标方法

当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?
我们还是通过程序来求证:

定义多个切面类:

为简单化,只写@Before和aAfter两个通知:

 @Component
 public class AspectDemo1 {
     @Pointcut("execution(* com.example.demo.controller.*.*(..))")
     private void pt(){}

     //前置通知
     @Before("pt()")
     public void doBefore() {
        log.info("执⾏  AspectDemo1 -> Before ⽅法");
     } 

     //后置通知
     @After("pt()")
     public void doAfter() {
        log.info("执⾏  AspectDemo1 -> After ⽅法");
     }
 }
 @Component
 public class AspectDemo2 {
     @Pointcut("execution(* com.example.demo.controller.*.*(..))")
     private void pt(){}

     //前置通知
     @Before("pt()")
     public void doBefore() {
        log.info("执⾏  AspectDemo2 -> Before ⽅法");
     } 

     //后置通知
     @After("pt()")
     public void doAfter() {
        log.info("执⾏  AspectDemo2 -> After ⽅法");
     }
 }
 @Component
 public class AspectDemo3 {
     @Pointcut("execution(* com.example.demo.controller.*.*(..))")
     private void pt(){}

     //前置通知
     @Before("pt()")
     public void doBefore() {
        log.info("执⾏  AspectDemo3 -> Before ⽅法");
     } 

     //后置通知
     @After("pt()")
     public void doAfter() {
        log.info("执⾏  AspectDemo3 -> After ⽅法");
     }
 }

运行程序,访问接口:

http://127.0.0.1:8080/test/t1

观察日志:
 

通过上述程序的运行结果,可以看出:
存在多个切面类时,默认按照切面类的类名字母排序:
@Before通知:字母排名靠前的先执行
@After通知:字母排名靠前的后执行
但这种方式不方便管理,我们的类名更多还是具备一定含义的.
Spring给我们提供了一个新的注解来控制这些切面通知的执行顺序:@Order

使用方式如下:
 

 @Aspect
 @Component
 @Order(2)
 public class AspectDemo1 {
     //...代码省略 
 }

 @Aspect
 @Component
 @Order(1)
 public class AspectDemo2 {
     //...代码省略 
 }

 @Aspect
 @Component
 @Order(3)
 public class AspectDemo3 {
     //...代码省略 
 }

重新运行程序,访问接口:

http://127.0.0.1:8080/test/t1

观察日志:

通过上述程序的运行结果,得出结论:
@Order注解标识的切面类,执行顺序如下:
@Before通知:数字越小先执行
@After通知:数字越大先执行
@Order控制切面的优先级,先执行优先级较高的切面,再执行优先级较低的切面,最终执行目标方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/740196.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JDBC的概念 ,核心API的介绍 , 注册驱动介绍

第一章 JDBC 1、JDBC的概念 目标 能够掌握JDBC的概念能够理解JDBC的作用 讲解 客户端操作MySQL数据库的方式 使用第三方客户端来访问MySQL&#xff1a;SQLyog、Navicat 使用MySQL自带的命令行方式 通过Java来访问MySQL数据库&#xff0c;今天要学习的内容 如何通过Java代…

时间?空间?复杂度??

1.什么是时间复杂度和空间复杂度&#xff1f; 1.1算法效率 算法效率分析分为两种&#xff1a;第一种是时间效率&#xff0c;第二种是空间效率。时间效率被称为时间复杂度&#xff0c;而空间效率被称为空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度&#xff0c;而空…

会声会影视频剪辑软件教程之剪辑软件波纹在哪 剪辑软件波纹怎么去掉 波纹剪辑是什么意思

波纹效果做不好&#xff0c;那一定是剪辑软件没选对。一款好用的视频剪辑软件&#xff0c;一定拥有多个制作波纹效果的方法。用户可以根据剪辑创作的需要&#xff0c;挑选最适合作品的波纹效果来使用。有关剪辑软件波纹在哪&#xff0c;剪辑软件波纹怎么去掉的问题&#xff0c;…

使用Fiddler如何创造大量数据

在调试和分析网络流量时&#xff0c;您是否曾为无法深入了解请求和响应的数据而感到困惑&#xff1f;如果有一种工具可以帮助您轻松抓取和分析网络流量&#xff0c;您的工作效率将大大提升。Fiddler正是这样一款功能强大的抓包工具&#xff0c;广受开发者和测试人员的青睐。 Fi…

【日常开发之Windows共享文件】Java实现Windows共享文件上传下载

文章目录 Windows 配置代码部分Maven代码 Windows 配置 首先开启服务&#xff0c;打开控制面板点击程序 点击启用或关闭Windows功能 SMB1.0选中红框内的 我这边是专门创建了一个用户 创建一个文件夹然后点击属性界面&#xff0c;点击共享 下拉框选择你选择的用户点击添加…

CSS规则——font-face

font-face 什么是font-face&#xff1f; 想要让网页文字千变万化&#xff0c;仅靠font-family还不够&#xff0c;还要借助font-face&#xff08;是一个 CSS 规则&#xff0c;它允许你在网页上使用自定义字体&#xff0c;而不仅仅是用户系统中预装的字体。这意味着你可以通过提…

Vue父组件mounted执行完后再执行子组件mounted

// 创建地图实例 this.map new BMap.Map(‘map’) } } ... 现在这样可能会报错&#xff0c;因为父组件中的 map 还没创建成功。必须确保父组件的 map 创建完成&#xff0c;才能使用 this.$parent.map 的方法。 那么&#xff0c;现在的问题是&#xff1a;如何保证父组件 mo…

全空间数据处理

高精度三维数据往往因为体量巨大、数据标准不一、高保密性要求等&#xff0c;给数据的后期储存、处理、分析及展示造成巨大困扰。多源异构数据的客观存在性与数据无缝融合的困难性&#xff0c;为空间信息数据和业务过程中其他文件的有效管理与共享制造了诸多障碍。 随着数字孪…

数据库断言-数据库更新

数据库更新的步骤和查询sql的步骤一致 1、连接数据库 驱动管理器调用连接数据库方法&#xff08;传入url&#xff0c;user&#xff0c;password&#xff09;&#xff0c;赋值给变量 2、操作数据库 connection调用参数化方法&#xff0c;对sql语法进行检查&#xff0c;存储s…

Elasticsearch:倒数排序融合 - Reciprocal rank fusion - 8.14

警告&#xff1a;此功能处于技术预览阶段&#xff0c;可能会在未来版本中更改或删除。语法可能会在正式发布之前发生变化。Elastic 将努力修复任何问题&#xff0c;但技术预览中的功能不受官方正式发布功能的支持 SLA 约束。 倒数排序融合 (reciprocal rank fusion - RRF) 是一…

Ltv 数据粘包处理

测试数据包的生成 校验程序处理结果和原始的日志保温解析是否一致 程序粘包分解正常

Java数据结构4-链表

1. ArrayList的缺陷 由于其底层是一段连续空间&#xff0c;当在ArrayList任意位置插入或者删除元素时&#xff0c;就需要将后序元素整体往前或者往后搬移&#xff0c;时间复杂度为O(n)&#xff0c;效率比较低&#xff0c;因此ArrayList不适合做任意位置插入和删除比较多的场景…

OS中断机制-外部中断触发

中断函数都定义在中断向量表中,外部中断通过中断跳转指令触发中断向量表中的中断服务函数,中断指令可以理解为由某个中断寄存器的状态切换触发的汇编指令,这个汇编指令就是中断跳转指令外部中断通过在初始化的时候使能对应的中断服务函数如何判断外部中断被触发的条件根据Da…

【zip密码】忘了zip密码,怎么办?

Zip压缩包设置了密码&#xff0c;解压的时候就需要输入正确对密码才能顺利解压出文件&#xff0c;正常当我们解压文件或者删除密码的时候&#xff0c;虽然方法多&#xff0c;但是都需要输入正确的密码才能完成。忘记密码就无法进行操作。 那么&#xff0c;忘记了zip压缩包的密…

Windows资源管理器down了,怎么解

ctrlshiftesc 打开任务管理器 文件 运行新任务 输入 Explorer.exe 资源管理器重启 问题解决 桌面也回来了

vue如何引入图标

方法1&#xff1a;iconify/vue pnpm add iconify/vue -D 网址&#xff1a;https://icon-sets.iconify.design/ 使用哪个需要安装 如下截图,安装指令&#xff1a; > npm install iconify/icons-gg在使用的页面引入 import { Icon } from “iconify/vue”; <template>…

LabVIEW与C#相互调用dll

C#调用LabVIEW创建的dll 我先讲LabVIEW创建自己的.net类库的方法吧&#xff0c;重点是创建&#xff0c;C#调用的步骤&#xff0c;大家可能都很熟悉了。 1、创建LabVIEW项目&#xff0c;并创建一个简单的add.vi&#xff0c;内容就是abc&#xff0c;各个接线端都正确连接就好。 …

机器学习之逻辑回归丨KNN测试

选择题 【 正确答案: A D】 A. B. C. D. 【 正确答案: B】 A. B. C. D. 【 正确答案: C, D】 A. B. C. D. 假设我们三个类别中心&#xff0c;若某测试样本为&#xff0c;它的 c ( i ) c^{(i)} c(i)是多少&#xff1f; 【 正确答案: B】 A.1 B.2 C.3 D.不确定 假设你…

UE5 场景物体一键放入蓝图中

场景中&#xff0c;选择所有需要加入到蓝图的模型或物体。 点击 蓝图按钮&#xff0c;点击“将选项转换为蓝图” 在创建方法中&#xff0c;选择“子Actor”或着 “获取组件” 如果需要保持相对应的Actor的父子级别&#xff08;多层&#xff09;&#xff0c;那么选择“获取组件…

如何在Linux下使用git(几步把你教会)

目录 一、注册github账号 二、新建项目 1.点击右上角自己的头像&#xff0c;然后点击Your repositories。 2.点击New。 3.配置新项目信息。 4.点击Create repository即可成功创建。 三、安装git 四、配置git 五、初始化git仓库 1.先进入想要使用git的目录。 2.初始化…