【Spring AOP 源码解析前篇】什么是 AOP | 通知类型 | 切点表达式| AOP 如何使用

前言(关于源码航行)
在这里插入图片描述

在准备面试和学习的过程中,我阅读了还算多的源码,比如 JUC、Spring、MyBatis,收获了很多代码的设计思想,也对平时调用的 API 有了更深入的理解;但过多散乱的笔记给我的整理复习带来了比较大的麻烦。
📋 在 C 站零零散散发了 JUC 的源码解析和集合源码解析,收到了很多朋友的喜爱,这里我准备将一些源码解析的文章整合起来,为了方便阅读和归纳在这里整合成目录:源码航行阅读目录,大家感兴趣的话可以关注一下!
————————————————

第一篇:基础知识介绍

这一部分我们来谈一下关于 Spring AOP 的浮在表面上的知识,比如什么是 AOP、它有什么好处、如何使用等等

为什么需要 AOP?

AOP(Aspect Oriented Programming),意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

举一个简单的例子,如果我们想要在每次调用一个类中的方法之前输出一个日志,那其实是挺容易实现的,画出流程图就是这个样子:
在这里插入图片描述

那如果我们要在这些方法之前加一个鉴权呢?同样,按照前面的方式,无非就是多 copy 一次嘛

在这里插入图片描述

就这样,通过多重 copy 的方式,最终完成了这个业务,而这也就代表着,每次扩展新的方法,你都要去进行 n 次复制粘贴;每次要更改鉴权或者日志的逻辑,你都要去进行 n 次复制粘贴;每次。。。。。。

听起来都让人非常头大,但如果我们将逻辑改成这样呢?

在这里插入图片描述

我们将这两个方法封装成一个公共方法,每次在方法之前去调用这个公共的方法,不就实现了可维护性吗,这其实就有一点切面的感觉了。

这样,我们虽然每次都不用去复制代码了,但还是需要在我们需要的位置去调用这个接口,而调用这个代码的位置其实就是 切面。我们用具体的代码来看一下,下面实现了一个统一的接口 BeforeAdvice,然后在每个方法之前去调用它。

public interface BeforeAdvice {void before();
}public class LoggingBeforeAdvice implements BeforeAdvice {@Overridepublic void before() {System.out.println("方法调用之前的日志");}
}public class MyService {private BeforeAdvice beforeAdvice = new LoggingBeforeAdvice();public void myMethodA() {beforeAdvice.before();// 业务逻辑System.out.println("执行 myMethod1A");}public void myMethodB() {beforeAdvice.before();// 业务逻辑System.out.println("执行 myMethodB");}
}

到这里,我们就自己实现了一个简单的 AOP;但每次都这样去创建一个这样的类,其实也是非常复杂的,这时候我们就可以借助 Spring AOP 的力量,它帮我们实现了简单、直观、极其易于配置的切面编程方式。

AOP 概念辨析

在正式讲解如何使用 Spring AOP 之前,我们来讲点无聊的内容,关于 AOP 的概念辨析;这部分内容是一些术语,但是如果能理解它们,就会对 AOP 整个流程有较为清晰的把握。

连接点(Join Point):连接点是程序执行中的一个点,这个点可以是方法调用、方法执行、异常抛出等。在 Spring AOP 中,连接点主要是指方法的调用或执行。连接点是通知的实际应用点。

切点(PointCut):由于连接点可能很多(比如一个类中的所有方法),想要把所有连接点罗列出现显然有些困难;切点则定义了在应用通知的连接点的集合。切点通过切点表达式(例如:execution(* com.example.service.*.*(..)))来指定匹配的方法和类。切点表达式用于筛选连接点,使得通知只在特定的连接点上执行。

通知(Advice):通知是在切点处执行的代码。通知定义了具体的横切逻辑,决定了在方法执行的什么阶段(之前、之后、环绕等)插入横切逻辑。通知有五种类型,我们会在下一部分进行详细的了解;通知就是在 何时 执行 怎样 的逻辑。

切面(Aspect):切面是 AOP 的核心模块,它封装了跨越多个类的关注点,例如日志记录、事务管理或安全控制。切面通过通知(Advice)和切点(Pointcut)来定义在何时、何地应用这些关注点;可以将切面看作是切点(Pointcut)和通知(Advice)的组合。切面定义了在何处(切点)以及何时(通知)应用横切逻辑。

五种通知类型

在 Spring AOP 中,通知(Advice)是指在程序执行过程中插入的代码,它定义了在何时以及在什么情况下进行切面的操作。通知是切面中的实际动作部分,是横切关注点的具体实现;直观来说就是要插入的那一组方法。

除了上面提到的在执行方法之前执行的 Before Advice,还有其他四种类型的通知,也就是说 Spring AOP 为我们提供了五个插入代码的位置选择。

前置通知(Before Advice)

在目标方法执行之前执行的通知。可以用来执行日志记录、安全检查等。

@Aspect
@Component
public class LoggingAspect {@Before("execution(* com.example.service.*.*(..))")public void beforeAdvice() {System.out.println("前置通知:方法调用之前执行");}
}

后置通知(After Advice)

在目标方法执行之后执行的通知,无论方法是成功返回还是抛出异常。常用于清理资源等。

@Aspect
@Component
public class LoggingAspect {@After("execution(* com.example.service.*.*(..))")public void afterAdvice() {System.out.println("后置通知:方法调用之后执行");}
}

返回后通知(After Returning Advice)

在目标方法成功返回结果之后执行的通知。可以用来记录返回值或对返回值进行处理。

@Aspect
@Component
public class LoggingAspect {@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")public void afterReturningAdvice(Object result) {System.out.println("返回后通知:方法返回值为 " + result);}
}

抛出异常后通知(After Throwing Advice)

在目标方法抛出异常后执行的通知。可以用来记录异常信息或执行异常处理逻辑。

@Aspect
@Component
public class LoggingAspect {@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "exception")public void afterThrowingAdvice(Exception exception) {System.out.println("抛出异常后通知:异常为 " + exception.getMessage());}
}

环绕通知(Around Advice)

环绕通知在目标方法执行的前后都执行,可以完全控制目标方法的执行,包括决定是否执行目标方法,以及在目标方法执行前后添加自定义逻辑。环绕通知最为强大和灵活。

@Aspect
@Component
public class LoggingAspect {@Around("execution(* com.example.service.*.*(..))")public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕通知:方法调用之前");Object result = joinPoint.proceed();  // 执行目标方法System.out.println("环绕通知:方法调用之后");return result;}
}

这五种通知方式的执行顺序是这样的:

  • 前置通知(Before Advice)
  • **环绕通知(Around Advice)**的前半部分
  • 目标方法执行
  • **环绕通知(Around Advice)**的后半部分
  • 返回后通知(After Returning Advice)(如果目标方法成功返回)
  • 抛出异常后通知(After Throwing Advice)(如果目标方法抛出异常)
  • 后置通知(After Advice)

切点表达式

前面提到过,切面直观来讲就是插入方法的位置;在前面五种通知类型中,我们已经看到了如何通过注解选择方法的执行位置,但是诸如
* com.example.service.*.*(..)) 这样,定位方法位置的格式其实是没有提及的,这一部分重点来讲一下如何配置方法的位置。

切点则表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。

切点表达式由以下几部分组成:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

其中,各部分的含义如下,注意上面的问号(?)表示可选

  • execution:指定切点类型为方法执行。
  • modifiers-pattern:可选,方法的访问修饰符,如 publicprotectedprivate。通常省略不写,表示任意访问修饰符。
  • ret-type-pattern:方法的返回类型模式,例如 voidString、``(任意返回类型)。
  • declaring-type-pattern:可选,方法所在类的全限定名称模式,如 com.example.service.*。指定要匹配的类或包。
  • name-pattern:方法名称模式,如 Serviceget*。支持通配符 ``(匹配任意字符序列)和 ..(匹配任意数量和类型的参数)。
  • param-pattern:方法参数模式,如 ()(无参数)、(*)(一个任意类型的参数)、(..)(任意数量和类型的参数)。
  • throws-pattern:可选,方法可能抛出的异常模式。

下面来做几个小练习

1)指定 com.example.service 包下的所有类的所有方法:

@Pointcut("execution(* com.example.service.*.*(..))")
private void serviceMethods() {}

2)com.example.service 包下所有类的方法中第一个参数为 String 类型的方法

@Pointcut("execution(* com.example.service.*.*(String, ..))")
private void stringParamMethods() {}

通过上面的练习,相信大家对切点表达式的书写方式有了一定的掌握,下面我们来看看,除了 execution 方法执行切点,Spring 还为我们提供了哪些指定切点的方式

within:限定匹配特定类型的连接点。within(com.example.service.*) 匹配 com.example.service 包及其子包中所有方法。

this:限定匹配特定类型的 bean。this(com.example.service.MyService) 匹配实现 com.example.service.MyService 接口的 bean。

target:限定匹配特定类型的目标对象。target(com.example.service.MyService) 匹配目标对象是 com.example.service.MyService 的连接点。

args:限定匹配特定参数类型的方法。args(String, ..) 匹配第一个参数是 String 类型的方法。

@annotation:限定匹配特定注解的方法。@annotation(org.springframework.transaction.annotation.Transactional) 匹配标注有 @Transactional 注解的方法。

因为 within、this 和 target,都可以通过 execution 作为一定程度上的替代,所以这里我们重点关注一下匹配特定注解的方式,即 @annotation 即可。

但同时,这些表达式其实是可以共用的,比如通过这样的方式:

@Before("execution(* com.example.service.UserService.getUserById(int)) && args(userId)")public void logBeforeGetUserById(JoinPoint joinPoint, int userId) {System.out.println("Before calling getUserById with userId: " + userId);}

上面的 userId 参数是通过切点表达式中的 args(userId) 指定的,所以在方法体内可以直接使用 userId 参数来获取方法执行时的具体值;但如果仅仅使用 joinPoint 的话就需要 getArgs() 再拿取参数了;这里只涉及写法上的偏好,我们平时使用的大部分的内容通过 execution 和 @annotation 都是可以实现的。

正式使用

通过前面的介绍,我们已经辨析了 AOP 的基本概念,了解了控制何时执行逻辑的通知类型(Advice),定义在什么位置执行的切点表达式(PointCut),下面我们正式来尝试使用 AOP 来解决一些现实的问题。

就举一个前面提到的日志记录的 AOP 吧

定义一个简单的服务类

@Service
public class UserService {public String getUserById(int userId) {// 模拟方法体return "User: " + userId;}
}

创建日志记录的切面类

@Aspect
@Component
public class LoggingAspect {@Before("execution(* com.example.service.UserService.getUserById(int))")public void logBeforeGetUserById(JoinPoint joinPoint) {// 获取方法参数Object[] args = joinPoint.getArgs();System.out.println("Before calling getUserById with userId: " + args[0]);}@AfterReturning(pointcut = "execution(* com.example.service.UserService.getUserById(int))", returning = "result")public void logAfterReturningGetUserById(JoinPoint joinPoint, Object result) {System.out.println("After returning from getUserById with result: " + result);}
}

然后我们去做一个简单的测试,可以看到如下的输出:

Before calling getUserById with userId: 123
After returning from getUserById with result: User: 123
User retrieved: User: 123

关于 JoinPoint 和 ProceedingJoinPoint

在 Spring AOP 中,JoinPointProceedingJoinPoint 是两个重要的接口,用于在切面中获取方法执行时的信息和控制方法执行;我们在书写切面逻辑的时候,需要的大部分参数或者方法信息等,都是从这里面获取的。

JoinPoint 接口

JoinPoint 接口是 Spring AOP 提供的一个核心接口,用于描述正在执行的连接点(join point),它可以用来获取方法的签名、参数等信息,但是不能直接控制方法的执行流程。

常用方法

Signature getSignature(); // 获取代表被通知方法签名的对象,可以进一步获取方法名、声明类型等信息。Object[] getArgs(); // 获取被通知方法的参数对象数组。Object getTarget(); // 获取目标对象,即被通知的目标类实例。Object getThis(); // 获取代理对象的引用,即代理对象本身。Object[] getArgs(); // 获取调用方法时传递的参数

其中有个特殊一点的是 Signature,方法签名接口:

public interface Signature {String toString(); // 返回方法的字符串表示形式。String toShortString(); // 返回方法的简短字符串表示形式。String toLongString(); // 返回方法的长字符串表示形式。String getName();// 获取方法名。 int getModifiers(); // 获取方法的修饰符,返回一个整数,具体取值需要通过 java.lang.reflect.Modifier 类来解析。Class getDeclaringType(); // 获取声明该方法的类的 Class 对象。String getDeclaringTypeName(); // 获取声明该方法的类的全限定名。
}

大家可以自己写个方法测试一下,这里就不过多赘述了。

ProceedingJoinPoint 接口

ProceedingJoinPoint 接口继承自 JoinPoint 接口,它扩展了 JoinPoint 接口,提供了控制方法执行流程的能力。通常在 Around Advice 中使用 ProceedingJoinPoint 来调用目标方法,并可以控制是否继续执行该方法,以及在执行前后进行额外的处理。

常用方法

Object proceed() throws Throwable; // 继续执行连接点(即目标方法),返回方法的返回值。Object proceed(Object[] args) throws Throwable; // 按照给定的参数继续执行连接点。

由于是继承,所以 JoinPoint 提供的方法,也都可以使用。

区别和用途

  • JoinPoint 主要用于获取方法的元数据信息,如方法名、参数等,不具备控制方法执行流程的能力。
  • ProceedingJoinPoint 继承自 JoinPoint,可以控制方法的执行流程,在 Around Advice 中使用,可以决定是否继续执行目标方法,以及在执行前后进行额外的处理。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PCIe驱动开发(2)— 第一个简单驱动编写和测试

PCIe驱动开发(2)— 第一个简单驱动编写和测试 一、前言 教程参考:02_实战部分_PCIE设备测试 教程参考:03_PCIe设备驱动源码解析 二、驱动编写 新建hello_pcie.c文件 touch hello_pcie.c然后编写内容如下所示: #i…

数学系C++(六七)

目录 * &指针与地址 void指针 指针可以等于: const 指向常量的指针 const int *px 常指针 int * const px 指向常量的常指针const 类型标识符 * const 指针名 指针加减: 指针恒等式 函数指针【待续】 指针型函数: 指向函数的…

MuLan:模仿人类画家的多对象图像生成

在图像生成领域,处理包含多个对象及其空间关系、相对大小、重叠和属性绑定的复杂提示时,现有的文本到图像模型仍面临挑战:当文本提示中包含多个对象,并且这些对象之间存在特定的空间关系时,现有模型往往难以准确地捕捉…

土豆炒肉做法

菜单:土豆、葱、铁辣子、纯瘦肉、淀粉、生抽、酱油、刀、案板、十三香、盐巴、擦板 流程: 洗土豆,削皮,擦成条,用凉水过滤两遍淀粉,顺便放个燥里洗肉,切成条,按照生抽、酱油、淀粉、…

Maven依赖管理项目构建工具

目录 文章目录 目录一、Maven简介1、为什么学习Maven1.1、Maven是一个依赖管理工具1.2、Maven是一个构建工具1.3、结论2. Maven介绍3. Maven软件工作原理模型图(了解)二、Maven安装和配置1. Maven安装2. Maven环境配置3. Maven功能配置4. IDEA配置本地Maven软件三、基于IDEA创…

银行信用卡风险大数据分析与挖掘2024

银行信用卡风险大数据分析与挖掘 使用excel数据挖掘功能完成 一、信用卡客户信用等级影响因素分析与挖掘 基于客户信用记录表 1. 数据预处理 浏览数据 客户等级占比,其中优质客户占比较少,风险客户很多,分析影响客户信用等级的原因 年…

spring boot读取yml配置注意点记录

问题1:yml中配置的值加载到代码后值变了。 现场yml配置如下: type-maps:infos:data_register: 0ns_xzdy: 010000ns_zldy: 020000ns_yl: 030000ns_jzjz: 040000ns_ggglyggfwjz: 050000ns_syffyjz: 060000ns_gyjz: 070000ns_ccywljz: 080000ns_qtjz: 090…

ASRock Creator系列GPU:为AI推理及多GPU系统打造,采用16针电源接口的Radeon RX 7900系列显卡

ASRock 正在筹备推出专为人工智能推理和多GPU系统设计的AMD GPU——Creator系列显卡。这一系列显卡采用双槽位、吹风式设计,并配备16针电源连接器,首发产品包括基于Navi 31架构的AMD Radeon RX 7900XTX和RX 7900 XT型号。这些原属于WS系列的显卡最初在20…

C++初学者指南-5.标准库(第一部分)--迭代器

C初学者指南-5.标准库(第一部分)–迭代器 Iterators 文章目录 C初学者指南-5.标准库(第一部分)--迭代器 Iterators1.默认正向迭代器2.反向迭代器3.基于迭代器的循环4.示例:交换相邻的一对元素5.迭代器范围6.迭代器范围中的元素数量7. 总结:迭代器 指向某…

Sequelize 操作 MySQL 数据库

安装 npm install --save sequelize安装驱动程序: npm install --save mysql2连接到数据库 要连接到数据库,必须创建一个 Sequelize 实例. 这可以通过将连接参数分别传递到 Sequelize 构造函数或通过传递一个连接 URI 来完成: const {Sequelize} re…

【C++知识点总结全系列 (06)】:STL六大组件详细总结与分析- 配置器、容器、迭代器、适配器、算法和仿函数

STL六大组件目录 前言1、配置器(1)What(2)Why(3)HowA.调用new和delete实现内存分配与销毁B.STL Allocator (4)allocator类A.WhatB.HowC.allocator的算法 2、容器(1)What(2)Which(有哪些容器)(3)序列容器(顺序容器)A.WhichB.array&…

Vue+Xterm.js+WebSocket+JSch实现Web Shell终端

一、需求 在系统中使用Web Shell连接集群的登录节点 二、实现 前端使用Vue&#xff0c;WebSocket实现前后端通信&#xff0c;后端使用JSch ssh通讯包。 1. 前端核心代码 <template><div class"shell-container"><div id"shell"/>&l…

web缓存代理服务器

一、web缓存代理 web代理的工作机制 代理服务器是一个位于客户端和原始&#xff08;资源&#xff09;服务器之间的服务器&#xff0c;为了从原始服务器取得内容&#xff0c;客户端向代理服务器发送一个请求&#xff0c;并指定目标原始服务器&#xff0c;然后代理服务器向原始…

【NTN 卫星通信】Starlink基于终端用户的测量以及测试概述

1 概述 收集了一些starlink的资料&#xff0c;是基于终端侧部署在野外的一些测试以及测量结果。 2 低地球轨道卫星网络概述 低地球轨道卫星网络(lsn)被认为是即将到来的6G中真正实现全球覆盖的关键基础设施。本文介绍了我们对Starlink端到端网络特征的初步测量结果和观测结果&…

win11自动删除文件的问题,安全中心提示

win11自动删除文件的问题&#xff0c;解决方法&#xff1a; 1.点击任务栏上的开始图标&#xff0c;在显示的应用中&#xff0c;点击打开设置。 或者点击电脑右下角的开始也可以 2.点击设置。也可以按Wini打开设置窗口。 3.左侧点击隐私和安全性&#xff0c;右侧点击Windows安全…

尚品汇-(十四)

&#xff08;1&#xff09;提交git 商品后台管理到此已经完成&#xff0c;我们可以把项目提交到公共的环境&#xff0c;原来使用svn&#xff0c;现在使用git 首先在本地创建ssh key&#xff1b; 命令&#xff1a;ssh-keygen -t rsa -C "your_emailyouremail.com" I…

【SVN的使用-源代码管理工具-命令行的使用 Objective-C语言】

一、接下来,我们来说一个终端的命令行的使用, 1.我们说,你的电脑里边呢,有终端, 在Mac里边,你想新建一个txt,应该怎么写,对,打开文本编辑, 打开这个东西,写点儿东西,然后保存一下,保存的时候,你还要去选择格式, 现在,如果我们用命令行,可以更方便一些, 2.首…

数据结构(其一)--基础知识篇

1. 数据结构三要素 1.1 数据结构的运算 即&#xff0c;增删改查 1.2 数据结构的存储结构 2. 数据类型&#xff0c;抽象数据类型 数据类型&#xff1a; &#xff08;1&#xff09;. 原子类型&#xff1a;bool、int... &#xff08;2&#xff09;. 结构类型&#xff1a;类、…

【vue组件库搭建06】组件库构建及npm发包

一、格式化目录结构 根据以下图片搭建组件库目录 index.js作为入口文件&#xff0c;将所有组件引入&#xff0c;并注册组件名称 import { EButton } from "./Button"; export * from "./Button"; import { ECard } from "./Card"; export * fr…

ubuntu常用命令

常用命令 netstatpsps -auxps -eLf netstat netstat -tunlp这个命令用来显示网络连接、路由表和网络接口统计信息。 Proto (协议): 显示连接使用的协议&#xff0c;通常是 “tcp” 或 “udp”。 Recv-Q (接收队列): 表示接收缓冲区中未被进程读取的数据包数量。一般情况下&am…