8 SpringBoot进阶(上):AOP(面向切面编程技术)、AOP案例之统一操作日志

news2025/7/10 23:54:29

文章目录

  • 前言
  • 1. AOP基础
    • 1.1 AOP概述: 什么是AOP?
    • 1.2 AOP快速入门
    • 1.3 Spring AOP核心中的相关术语(面试)
  • 2. AOP进阶
    • 2.1 通知类型
      • 2.1.1 @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行(通知的代码在业务方法之前和之后都有)
      • 2.1.2 @Before:前置通知,此注解标注的通知方法在目标方法前被执行(通知的代码只放在业务方法之前)
      • 2.1.3 @AfterReturning : 后置(返回)通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行(通知的代码只放在业务方法之后)
      • 2.1.4 @After :后置最终通知,此注解标注的通知方法无论目标方法如何结束(正常返回或抛出异常),都会执行。(通知代码在 finally 的代码块之中)
      • 2.1.5 @AfterThrowing : 后置异常通知,此注解标注的通知方法在目标方法抛出异常后执行。(通知代码在捕获异常 catch(){}的代码块之中)
    • 2.2 通知顺序(如果多个切片类都操作一个目标方法会发生什么呢?)
      • 2.2.1 默认顺序规则
      • 2.2.2 使用Spring提供的@Order注解指定顺序规则
    • 2.3 切入点表达式
      • 2.3.1 execution
      • 2.3.2 @annotation(更灵活简单)
      • 2.3.3 @PointCut注解 切入点表达式的抽取 (用于标识切入点方法(公共的切入点表达式))
    • 2.4 连接点(连接点对象)
      • 2.4.1 JoinPoint连接点对象:对于除环绕通知外的其他所有通知,获取连接点信息只能使用JoinPoint
      • 2.4.2 ProceedingJoinPoint连接点对象:对于环绕通知,获取连接点信息只能使用ProceedingJoinPoint类型
  • 3. AOP案例:操作日志(重要!!!)
    • 3.1 需求:将案例中增、删、改相关接口的操作日志记录到数据库表中
    • 3.2 分析
    • 3.3 步骤
    • 3.4 实现
      • 3.4.1 准备工作
      • 3.4.2 编码实现


前言


1. AOP基础

在AOP基础这个阶段,我们首先介绍一下什么是AOP,再通过一个快速入门程序,让大家快速体验AOP程序的开发。最后再介绍AOP当中所涉及到的一些核心的概念。

1.1 AOP概述: 什么是AOP?

什么是AOP?

  • AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。

那什么又是面向方法编程呢,为什么又需要面向方法编程呢?来我们举个例子做一个说明:

比如,我们这里有一个项目,项目中开发了很多的业务功能。
在这里插入图片描述
然而有一些业务功能执行效率比较低,执行耗时较长,我们需要针对于这些业务方法进行优化。 那首先第一步就需要定位出执行耗时比较长的业务方法,再针对于业务方法再来进行优化。

此时我们就需要统计当前这个项目当中每一个业务方法的执行耗时。那么统计每一个业务方法的执行耗时该怎么实现?

可能多数人首先想到的就是在每一个业务方法运行之前,记录这个方法运行的开始时间。在这个方法运行完毕之后,再来记录这个方法运行的结束时间。拿结束时间减去开始时间,不就是这个方法的执行耗时吗?

以上分析的实现方式是可以解决需求问题的。但是对于一个项目来讲,里面会包含很多的业务模块,每个业务模块又包含很多增删改查的方法,如果我们要在每一个模块下的业务方法中,添加记录开始时间、结束时间、计算执行耗时的代码,就会让程序员的工作变得非常繁琐。

在这里插入图片描述

而AOP面向方法编程,就可以做到在不改动这些原始方法的基础上,针对特定的方法进行功能的增强。

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

我们要想完成统计各个业务方法执行耗时的需求,我们只需要定义一个模板方法,将记录方法执行耗时这一部分公共的逻辑代码,定义在模板方法当中,在这个方法开始运行之前,来记录这个方法运行的开始时间,在方法结束运行的时候,再来记录方法运行的结束时间,中间就来运行原始的业务方法。

在这里插入图片描述

而中间运行的原始业务方法,可能是其中的一个业务方法,比如:我们只想通过 部门管理的 list 方法的执行耗时,那就只有这一个方法是原始业务方法。 而如果,我们是先想统计所有部门管理的业务方法执行耗时,那此时,所有的部门管理的业务方法都是 原始业务方法。 那面向这样的指定的一个或多个方法进行编程,我们就称之为 面向切面编程。

简单了解了一下,相信已经发现了,这个AOP面向切面编程和我们在JavaSE里面学的动态代理技术很像,没错,这个技术的底层就是用动态代理实现的。

那此时,当我们再调用部门管理的 list 业务方法时啊,并不会直接执行 list 方法的逻辑,而是会执行我们所定义的 模板方法 , 然后再模板方法中:

  • 记录方法运行开始时间
  • 运行原始的业务方法(那此时原始的业务方法,就是 list 方法)
  • 记录方法运行结束时间,计算方法执行耗时

在这里插入图片描述

不论,我们运行的是那个业务方法,最后其实运行的就是我们定义的模板方法,而在模板方法中,就完成了原始方法执行耗时的统计操作 。(那这样呢,我们就通过一个模板方法就完成了指定的一个或多个业务方法执行耗时的统计)

而大家会发现,这个流程,我们是不是似曾相识啊?

对了,就是和我们之前所学习的动态代理技术是非常类似的。 我们所说的模板方法,其实就是代理对象中所定义的方法,那代理对象中的方法以及根据对应的业务需要, 完成了对应的业务功能,当运行原始业务方法时,就会运行代理对象中的方法,从而实现统计业务方法执行耗时的操作。

其实,AOP面向切面编程和OOP面向对象编程一样,它们都仅仅是一种编程思想,而动态代理技术是这种思想最主流的实现方式。而Spring的AOP是Spring框架的高级技术,旨在管理bean对象的过程中底层使用动态代理机制,对特定的方法进行编程(功能增强)

AOP的优势:

  1. 减少重复代码
  2. 提高开发效率
  3. 维护方便

1.2 AOP快速入门

我们一般会建一个aop包来存放所有的模版方法:

在了解了什么是AOP后,我们下面通过一个快速入门程序,体验下AOP的开发,并掌握Spring中AOP的开发步骤。

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

实现步骤:

  1. 导入依赖:在pom.xml中导入AOP的依赖
  2. 编写AOP程序:针对于特定方法根据业务需要进行编程

为演示方便,可以自建新项目或导入提供的springboot-aop-quickstart项目工程

pom.xml

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

AOP程序:TimeAspect

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

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

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

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

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

        return result;
    }
}

重新启动SpringBoot服务测试程序(先把过滤器和拦截器都关了):

  • 根据id查询员工
    在这里插入图片描述
    在这里插入图片描述

我们也可以下断点看看:
在这里插入图片描述

我们通过AOP入门程序完成了业务方法执行耗时的统计,那其实AOP的功能远不止于此,常见的应用场景如下:

  • 记录系统的操作日志
  • 权限控制
  • 事务管理:我们前面所讲解的Spring事务管理,底层其实也是通过AOP来实现的,只要添加@Transactional注解之后,AOP程序自动会在原始方法运行前先来开启事务,在原始方法运行完毕之后提交或回滚事务

这些都是AOP应用的典型场景。

通过入门程序,我们也应该感受到了AOP面向切面编程的一些优势:

  • 代码无侵入:没有修改原始的业务方法,就已经对原始的业务方法进行了功能的增强或者是功能的改变

  • 减少了重复代码

  • 提高开发效率

  • 维护方便

1.3 Spring AOP核心中的相关术语(面试)

这部分建议写学完所有最后再来看才会清晰,不然压根不知道在讲什么

通过SpringAOP的快速入门,感受了一下AOP面向切面编程的开发方式。下面我们再来学习AOP当中涉及到的一些相关术语。

  • (1) 切面(Aspect):简单理解为一个包含了通知(advice)和切点(poincut)的类
    其实就是定义了一个Java 类,里面包含了通知(advice)和切点(poincut)定义了在何处以及何时执行通知,将切面的一些东西模块化了,即定义横切关注点的模块,封装了不同模块共享的功能.
    执行后或抛出异常时运行。
  • (2) 通知(Advice): 切面中的实际逻辑,即在连接点上执行的操作。通知可以在方法执行前(就是切面类里面使用了通知注解的哪些方法,所以通知就是方法)
    • 前置通知(Before advice): 在目标方法执行前执行。
    • 后置通知(After returning advice):在目标方法成功执行后执行。
    • 后置异常通知(After throwing advice):在目标方法抛出异常后执行。
    • 后置最终通知(After (finally) advice):无论目标方法如何结束(正常返回或抛出异常),都会执行。
    • 环绕通知(Around advice):在目标方法执行前后都执行,并且可以控制目标方法的执行过程,环绕通知可以用作日志打印或者权限校验。
  • (3) 切点(Pointcut): 切点是一个表达式,用于定义在哪些连接点上执行通知,简单理解就是通过这个表达式可以找到想要织入的哪些方法。
  • (4) 连接点(Join point): 连接点是程序执行过程中可以应用切面的点,例如方法的调用、方去的执行、异常的抛出等,可以拿到切入方法名等诸多属性。
    其实就是JoinPoint和ProceedingJoinPoint这两个连接点对象,我们可以从这个连接点对象中获取到目标方法的各种信息,目标方法的返回值、方法名、参数列表等(所以我们也可以简单理解连接点就是可以被aop控制的哪些目标方法)
  • (5) 目标对象(Target Object): 被切面增强的对象,也就是原本的业务类
    就是目标方法所在的哪些类对象,就称为目标对象
  • (6) 代理(Proxy): Spring AOP 通过生成代理对象来增强目标对象的方法。代理对象包含目对象的原始方法和增强逻辑。
    Spring AOP底层是动态代理,当然会有代理对象
  • (7) 织入(Weaving): 将切面应用到目标对象上的过程。Spring AOP 是在运行时进行织入的
    把通知应用到目标对象上的过程就称为织入

2. AOP进阶

AOP的基础知识学习完之后,下面我们对AOP当中的各个细节进行详细的学习。主要分为4个部分:

  1. 通知类型
  2. 通知顺序
  3. 切入点表达式
  4. 连接点

我们先来学习第一部分通知类型。

2.1 通知类型

2.1.1 @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行(通知的代码在业务方法之前和之后都有)

2.1.2 @Before:前置通知,此注解标注的通知方法在目标方法前被执行(通知的代码只放在业务方法之前)

2.1.3 @AfterReturning : 后置(返回)通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行(通知的代码只放在业务方法之后)

2.1.4 @After :后置最终通知,此注解标注的通知方法无论目标方法如何结束(正常返回或抛出异常),都会执行。(通知代码在 finally 的代码块之中)

2.1.5 @AfterThrowing : 后置异常通知,此注解标注的通知方法在目标方法抛出异常后执行。(通知代码在捕获异常 catch(){}的代码块之中)

在入门程序当中,我们已经使用了一种功能最为强大的通知类型:Around环绕通知。

@Around("execution(* com.itheima.service.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
   
    //记录方法执行开始时间
    long begin = System.currentTimeMillis();
    //执行原始方法
    Object result = pjp.proceed();
    //记录方法执行结束时间
    long end = System.currentTimeMillis();
    //计算方法执行耗时
    log.info(pjp.getSignature()+"执行耗时: {}毫秒",end-begin);
    return result;
}

只要我们在通知方法上加上了@Around注解,就代表当前通知是一个环绕通知。

Spring中AOP的通知类型:

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行(通知的代码在业务方法之前和之后都有
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行(通知的代码只放在业务方法之前
  • @AfterReturning : 后置(返回)通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行(通知的代码只放在业务方法之后
  • @After :(后置)最终通知,此注解标注的通知方法无论目标方法如何结束(正常返回或抛出异常),都会执行。(通知代码在 finally 的代码块之中
  • @AfterThrowing : (后置)异常通知,此注解标注的通知方法在目标方法抛出异常后执行。(通知代码在捕获异常 catch(){}的代码块之中

下面我们通过代码演示,来加深对于不同通知类型的理解:

@Slf4j
@Component
@Aspect
public class MyAspect1 {
   
    //前置通知
    @Before("execution(* com.itheima.service.*.*(..))")
    public void before(JoinPoint joinPoint){
   
        log.info("前置通知中代码执行了 ...");

    }

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

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();

        //原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了

        log.info("环绕通知中after代码执行了 ...");
        return result;
    }

    //后置最终通知
    @After("execution(* com.itheima.service.*.*(..))")
    public void after(JoinPoint joinPoint){
   
        log.info("最终通知中代码执行了 ...");
    }

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

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

重新启动SpringBoot服务,进行测试:

1. 没有异常情况下:

  • 使用postman测试查询所有部门数据

在这里插入图片描述

  • 查看idea中控制台日志输出
    在这里插入图片描述

程序没有发生异常的情况下,@AfterThrowing标识的异常通知方法不会执行。

2. 出现异常情况下:

修改DeptServiceImpl业务实现类中的代码: 添加异常

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
   
    @Autowired
    private DeptMapper deptMapper

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day01_Java基础

文章目录 day01_Java基础一、今日课程内容二、Java语言概述&#xff08;了解&#xff09;1、Java语言概述2、为什么要学习Java语言3、Java平台版本说明4、Java特点 三、Java环境搭建&#xff08;操作&#xff09;1、JDK和JRE的概述2、JDK的下载和安装3、IDEA的安装4、IDEA的启动…

cursor 弹出在签出前,请清理仓库工作树 窗口

问题出现的背景&#xff1a;是因为我有两台电脑开发&#xff0c;提交后&#xff0c;另一个电脑的代码是旧的&#xff0c;这个时候我想拉取最新的代码&#xff0c;就会出现如下弹窗&#xff0c;因为这个代码暂存区有记录或者工作区有代码的修改&#xff0c;所以有冲突&#xff0…

详解直方图均衡化

直方图均衡化&#xff08;Histogram Equalization&#xff09; 是图像处理中一种常用的对比度增强技术&#xff0c;通过调整图像的灰度分布&#xff0c;使得图像的直方图尽可能均匀分布&#xff0c;从而提高图像的对比度和细节表现。以下是直方图均衡化的原理详解&#xff1a; …

Kibana:Spotify Wrapped 第二部分:深入挖掘数据

作者&#xff1a;来自 Elastic Philipp Kahr 我们将比以往更深入地探究你的 Spotify 数据并探索你甚至不知道存在的联系。 在由 Iulia Feroli 撰写的本系列的第一部分中&#xff0c;我们讨论了如何获取 Spotify Wrapped 数据并在 Kibana 中对其进行可视化。在第 2 部分中&#…

C++—类与对象(中)

目录 1、类的6个默认成员函数 2、构造函数 构造函数的特性 3、初始化列表 4、析构函数 概念 5、拷贝构造函数 6、运算符重载 7、赋值运算符重载 赋值运算符重载格式 8、前置和后置重载 9、const修饰的成员 10、取地址及const取地址重载 1、类的6个默认成员函数 一…

MySQL 事务笔记

MySQL 事务笔记 目录 事务简介事务操作事务四大特性并发事务问题事务隔离级别总结 事务简介 事务&#xff08;Transaction&#xff09;是数据库操作的逻辑单元&#xff0c;由一组不可分割的SQL操作组成。主要用于保证&#xff1a; 多个操作的原子性&#xff08;要么全部成功…

网络空间安全(7)攻防环境搭建

一、搭建前的准备 硬件资源&#xff1a;至少需要两台计算机&#xff0c;一台作为攻击机&#xff0c;用于执行攻击操作&#xff1b;另一台作为靶机&#xff0c;作为被攻击的目标。 软件资源&#xff1a; 操作系统&#xff1a;如Windows、Linux等&#xff0c;用于安装在攻击机和…

HarmonyOS学习第11天:布局秘籍RelativeLayout进阶之路

布局基础&#xff1a;RelativeLayout 初印象 在 HarmonyOS 的界面开发中&#xff0c;布局是构建用户界面的关键环节&#xff0c;它决定了各个组件在屏幕上的位置和排列方式。而 RelativeLayout&#xff08;相对布局&#xff09;则是其中一种功能强大且灵活的布局方式&#xff0…

【2025年2月28日稳定版】小米路由器4C刷机Immortalwrt 23.05.4系统搭载mentohust 0.3.1插件全记录

小米路由器4C刷机Immortalwrt系统搭载mentohust插件全记录 首先将路由器按住后面的reset&#xff0c;用一个针插进去然后等待5s左右&#xff0c;松开&#xff0c;即可重置路由器。 然后要用物理网线物理连接路由器Lan口和电脑&#xff0c;并将路由器WAN口连接至网口。确保电脑…

【SpringBoot+Vue】博客项目开发二:用户登录注册模块

后端用户模块开发 制定参数交互约束 当前&#xff0c;我们使用MybatisX工具快速生成的代码中&#xff0c;包含了一个实体类&#xff0c;这个类中包含我们数据表中的所有字段。 但因为有些字段&#xff0c;是不应该返回到前端的&#xff0c;比如用户密码&#xff0c;或者前端传…

idea + Docker + 阿里镜像服务打包部署

一、下载docker desktop软件 官网下载docker desktop&#xff0c;需要结合wsl使用 启动成功的画面(如果不是这个画面例如一直处理start或者是stop需要重新启动&#xff0c;不行就重启电脑) 打包成功的镜像在这里&#xff0c;如果频繁打包会导致磁盘空间被占满&#xff0c;需…

ubuntu 20.04 安装labelmg

1. 下载安装包 下载链接&#xff1a;下载链接 2. 安装启动 # 创建labelImg的环境 conda create -n labelImg# 激活labelImg环境 source activate labelImg安装依赖 pip install pyqt5-dev-tools -i https://pypi.tuna.tsinghua.edu.cn/simple/cd requirements/pip install -…

Redis版本的EOL策略与升级路径(刷到别划走)

各位看官&#xff0c;刷到就点进来&#xff0c;大数据已经抓到你喽&#xff5e;&#x1f60a; 前言 在软件行业做服务端开发的我们&#xff0c;多多少少都会接触到Redis&#xff0c;用它来缓存数据、实现分布式锁等&#xff0c;相关八股文烂熟于心&#xff0c;但是往往会忽略具…

ExpMoveFreeHandles函数分析和备用空闲表的关系

第一部分&#xff1a;ExpMoveFreeHandles和备用空闲表的关系 ULONG ExpMoveFreeHandles ( IN PHANDLE_TABLE HandleTable ) { ULONG OldValue, NewValue; ULONG Index, OldIndex, NewIndex, FreeSize; PHANDLE_TABLE_ENTRY Entry, FirstEntry; EXHAND…

java项目之基于ssm的学籍管理系统(源码+文档)

项目简介 基于ssm的学籍管理系统实现了以下功能&#xff1a; 学生信息管理&#xff1a; 学生信息新增 学生信息修改 学籍异动管理&#xff1a; 学籍异动添加 学籍异动删除 学籍异动修改 学生学业管理&#xff1a; 学生学业添加 学生学业修改 学生学业删除 学院信息管理&am…

SpringBoot+Redis+Mybatis-plus黑马点评

短信登录 基于Session实现登录 流程&#xff1a; 发送短信验证码-->短信验证码注册登录-->校验登录状态&#xff08;保存用户到ThreadLocal&#xff0c;方便后续使用&#xff09; 不能每次请求服务都要进行登录状态校验&#xff0c;解决办法&#xff1a;拦截器 在Sp…

[STM32]从零开始的STM32 BSRR、BRR、ODR寄存器讲解

一、前言 学习STM32一阵子以后&#xff0c;相信大家对STM32 GPIO的控制也有一定的了解了。之前在STM32 LED的教程中也教了大家如何使用寄存器以及库函数控制STM32的引脚从而点亮一个LED&#xff0c;之前的寄存器只是作为一个引入&#xff0c;并没有深层次的讲解&#xff0c;在教…

DeepSeek-V3关键技术之一:DeepSeekMoE

DeepSeekMoE 是一种创新的大规模语言模型架构&#xff0c;旨在通过高效的计算流程和优化设计&#xff0c;在保持高性能的同时显著降低计算成本。 1. 架构设计 DeepSeekMoE 基于 Transformer 架构&#xff0c;融合了以下核心技术&#xff1a; 专家混合系统&#xff08;Mixture…

Android Activity启动流程详解

目录 Activity 启动流程详细解析 1. 应用层发起启动请求 1.1 调用 startActivity() 1.2 通过 Instrumentation 转发请求 2. 系统服务处理&#xff08;AMS 阶段&#xff09; 2.1 Binder IPC 通信 2.2 AMS 处理流程 2.3 跨进程回调 ApplicationThread 3. 目标进程初始化…

夜天之书 #106 Apache 软件基金会如何投票选举?

近期若干开源组织进行换届选举。在此期间&#xff0c;拥有投票权的成员往往会热烈讨论&#xff0c;提名新成员候选人和治理团队的候选人。虽然讨论是容易进行的&#xff0c;但是实际的投票流程和运作方式&#xff0c;在一个成员众多的组织中&#xff0c;可能会有不少成员并不清…