【JavaWeb后端学习笔记】Spring AOP面向切面编程
AOP
- 1、Spring AOP概述
- 2、SpringAOP快速入门
- 3、SpringAOP核心概念
- 4、通知类型
- 5、通知顺序
- 6、切入点表达式
- 6.1 execution方式
- 6.2 @annotation方式
- 7、连接点
1、Spring AOP概述
AOP:Aspect Oriented Programming,面向特定方法编程。
AOP是通过动态代理技术实现的。SpringAOP是Spring框架的高级技术,旨在管理Bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。
假设场景:现在需要优化一个刚开发好的系统,首先要记录每一个业务方法执行的时长,也就是在业务开始时记录开始时间,业务执行完毕时记录结束时间,时间差就是该业务的执行时间。只要在每一个业务方法中增加这个操作,就能记录所有业务方法的执行时间。但是由于业务可能非常多,这样做相当繁琐,工作量也大。这是可以通过AOP面向切面编程来优化该操作。
AOP会通过动态代理技术对原有方法进行改造。AOP首先会定义一个模板方法,在模板方法内定义在业务方法执行前记录开始时间,然后执行业务方法,业务方法执行后记录结束时间,获得时间差。这样就不需要对每个业务方法进行改造,而是通过AOP来改造了。
2、SpringAOP快速入门
以记录业务方法执行时间为例。
SpringAOP的使用分一下几步:
- 引入SpringAOP依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 编写AOP程序。首先定义一个类,在类上加@Component注解与@Aspect注解。@Component注解表明将这个类交给IOC容器管理,@Aspect声明当前类为AOP类。然后定义一个方法模板,在方法模板中记录开始时间,调用原始方法,再记录结束时间。注意给方法模板传入ProceedingJoinPoint类对象,通过该对象的proceed()方法能够调用原始方法。
- 给方法模板加切入点。指定哪些包里的那些接口和类中的方法需要通过AOP改造。
最后的简单实现如下:
@Slf4j
@Component // 交给IOC容器管理
@Aspect // 声明当前类为AOP类
public class TimeAspect {@Around("execution(* com.wrj.controller.*.*(..))") // 切入点表达式。指定com.wrj.controller下的所有接口和类中的所有方法都被改造public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {// 1. 记录开始时间long begin = System.currentTimeMillis();// 2. 调用原始方法运行Object result = joinPoint.proceed();// 3. 记录结束时间,计算方法耗时long end = System.currentTimeMillis();long times = end - begin;log.info(joinPoint.getSignature() + "方法执行耗时:{}", times + "ms");return result;}
}
3、SpringAOP核心概念
SpringAOP中有五大核心概念:
- 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息);
- 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法);
- 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用;
- 切面:Aspect,描述通知与切入点的对应关系(通知+切入点);
- 目标对象:Target,通知所应用的对象。
通过上面SpringAOP快速入门案例进行简单解释。
通知:在上面的案例中,需要记录方法的执行时间,因此需要先记录开始时间,再记录结束时间,最后计算执行时长,这三步共性代码就是通知。
切入点:案例中方法模板上加了一个注解,注解中写了一个切入点表达式。这个表达式是指定在com.wrj.controller包下的所有接口和类中的方法都被AOP改造。匹配的条件就是切入点。
@Around("execution(* com.wrj.controller.*.*(..))")
切面:切面是切入点和通知的组合。描述的是切入点与通知的关系。即切入点匹配到的方法需要执行通知中的公共代码。
连接点:在上面的案例中com.wrj.controller下的所有方法都是连接点,因为都可以被AOP改造。
目标对象:目标对象指通知应用的对象,此处指的是com.wrj.controller包下的类对象或接口实现类对象。
4、通知类型
通知类型有5种:
@Around
:环绕通知,此注解标注的通知方法在目标方法前、后都被执行;@Before
:前置通知,此注解标注的通知方法在目标方法前被执行;@After
:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行;- @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行;
- @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行。
在使用环绕通知@Around时需注意注意:
- @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
- @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。
5、通知顺序
如果有多个切面的切入点都匹配到了同一个目标方法,目标方法运行时,多个通知方法都会被执行。这时需要考虑通知顺序。
- 不同的切面类中,默认按照切面类的类名字排序:
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
- 用@Order(数字)加在切面类上来控制顺序
- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字小的后执行
6、切入点表达式
切入点表达式有两种形式:execution方式与@annotation方式
6.1 execution方式
execution方式主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配。
语法为:
execution(访问修饰符 返回值 包名.类名.方法名(方法参数全类名) throws 异常)
其中有几处可省略:
· 访问权限修饰符:可省略(比如:public、protected)
· 包名.类名.:可省略(注意类名后面也有点,不建议省略)
· throws异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
在切入点表达式中,可以使用通配符描述切入点:
* :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分。
.. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数。
- 在通知类型注解中通过切入点表达式设置切入点。
@Component
@Aspect
@Slf4j
public class DemoAspect {@Before("execution(* com.wrj.controller.*.*(..))")public void testBefore() {log.info("Before...前置通知");}@Around("execution(* com.wrj.controller.*.*(..))")public Object testAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around...环绕通知,原始方法执行前逻辑");Object result = joinPoint.proceed();log.info("Around...环绕通知,原始方法执行后逻辑");return result;}@After("execution(* com.wrj.controller.*.*(..))")public void testAfter() {log.info("Before...后置通知");}
}
- 提取公共切入点。同样通过切入点表达式设置切入点,但是提取公共切入点。可以通过定义一个空方法,在空方法上加入注解@PointCut指定要抽取的公共的切入点。
@Component
@Aspect
@Slf4j
public class DemoAspect {// 1. 定义一个空方法,加上@PointCut注解抽取公共切入点@Pointcut("execution(* com.wrj.controller.*.*(..))")public void pt() {}@Before("pt()") // 2. 在通知类型注解中加入抽取的切入点public void testBefore() {log.info("Before...前置通知");}@Around("pt()")public Object testAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around...环绕通知,原始方法执行前逻辑");Object result = joinPoint.proceed();log.info("Around...环绕通知,原始方法执行后逻辑");return result;}@After("pt()")public void testAfter() {log.info("Before...后置通知");}
}
- 引用其他切面类的切入点。前提是有一个切面类中已经抽取除了公共切入点。并且其他的切面类有足够的访问权限访问该设置了公共切入点的方法。直接在通知类型注解中通过 包名.类名.方法名() 的方式引用。
@Slf4j
@Component // 交给IOC容器管理
@Aspect // 声明当前类为AOP类
public class TimeAspect {@Around("com.wrj.aspect.DemoAspect.pt()") // 引用com.wrj.aspect包下,DemoAspect类中的pt()方法上设置的切入点public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {// 1. 记录开始时间long begin = System.currentTimeMillis();// 2. 调用原始方法运行Object result = joinPoint.proceed();// 3. 记录结束时间,计算方法耗时long end = System.currentTimeMillis();long times = end - begin;log.info(joinPoint.getSignature() + "方法执行耗时:{}", times + "ms");return result;}
}
- 通过 ||、! 、&& 逻辑运算符连接多个切入点表达式。
@Component
@Aspect
@Slf4j
public class DemoAspect {// 使用 || 运算连接两个切入点表达式@Pointcut("execution(String com.wrj.controller.DemoController.demoFilter()) ||" +"execution(String com.wrj.controller.DemoController.demo())")public void pt() {}@Before("pt()")public void testBefore() {log.info("Before...前置通知");}
}
6.2 @annotation方式
- 自定义注解。在使用@annotation方式前需要自定义一个注解。自定义注解为空注解即可,不需要定义属性。
@Retention(RetentionPolicy.RUNTIME) // @Retention描述注解什么时候生效。RetentionPolicy.RUNTIME表示运行时生效
@Target(ElementType.METHOD) // @Target描述作用在哪些地方。ElementType.METHOD表示作用在方法上
public @interface MyAnnotation {
}
- 通过@annotation方式编写切入点表达式。在@annotation中写入自定义注解的全类名。含义是匹配加了自定义注解的方法。
@Component
@Aspect
@Slf4j
public class DemoAspect {// 在@annotation中写入自定义注解的全类名@Pointcut("@annotation(com.wrj.annotation.MyAnnotation)")public void pt() {}@Before("pt()")public void testBefore() {log.info("Before...前置通知");}
}
- 在需要被AOP改动的方法上加上自定义注解。
@RestController
@RequestMapping("/demo")
@Slf4j
public class DemoController {@MyAnnotation // 加自定义注解@GetMappingpublic String demoFilter() {System.out.println("demoFilter接口执行...");return "执行结束";}@MyAnnotation // 加自定义注解@GetMappingpublic String demo() {System.out.println("demoFilter接口执行...");return "执行结束";}
}
execution方式和@annotation方式可以混用。
7、连接点
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
对于@Around 通知,获取连接点信息只能使用 ProceedingJoinPoint。
对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型。
如要使用原始方法,需要自己在模板方法形参中指定JoinPoint或ProceedingJoinPoint对象。
一些常用的方法:
// 1. 获取 目标对象类名
String className = joinPoint.getTarget().getClass().getName();
// 2. 获取 目标方法的方法名
String methodName = joinPoint.getSignature().getName();
// 3. 获取 目标方法运行时传入的参数
Object[] args = joinPoint.getArgs();
// 4. 放行 目标方法执行并且返回目标方法运行的返回值
Object result = joinPoint.proceed();
注意:若模板方法需要返回原始方法返回的值,则模板方法的返回值类型需定义为Object。
相关文章:

【JavaWeb后端学习笔记】Spring AOP面向切面编程
AOP 1、Spring AOP概述2、SpringAOP快速入门3、SpringAOP核心概念4、通知类型5、通知顺序6、切入点表达式6.1 execution方式6.2 annotation方式 7、连接点 1、Spring AOP概述 AOP:Aspect Oriented Programming,面向特定方法编程。 AOP是通过动态代理技术…...

6.584-Lab5B
6.584-Lab5B Reference CodeReference BlogHomeworkMyself Code Sharded Key/Value Service 梗概 这个图是我从上面参考blog中拿来的,觉得做的不错,借助这张图来讲解一下需要一个什么样的 Service。 ShardCtrler Client: 接收来自客户发出的命…...

OceanBase 的探索与实践
作者:来自 vivo 互联网数据库团队- Xu Shaohui 本文总结了目前我们遇到的痛点问题并通过 OceanBase 的技术方案解决了这些痛点问题,完整的描述了 OceanBase 的实施落地,通过迁移到 OceanBase 实践案例中遇到的问题与解决方案让大家能更好的了…...

安卓调试环境搭建
前言 前段时间电脑重装了系统,最近准备调试一个apk,没想到装环境的过程并不顺利,很让人火大,于是记录一下。 反编译工具下载 下载apktool.bat和apktool.jar 官网地址:https://ibotpeaches.github.io/Apktool/install…...

动画Lottie
Lottie简介 Lottie是一个Airbnb 开发的用于Android,iOS,Web和Windows的库,用于解析使用Bodymovin导出为json的Adobe After Effects动画,并在移动设备和网络上呈现 — GitHub Lottie主要特性 After Effects 兼容性: …...

C++感受14-Hello Object 封装版 - 上
1. 封装即约束——封装和派生、多态的本质区别 一门计算机语言,要如何帮助程序员写出优秀的代码?两个方法:一是给程序员更多能力,二是给程序员更多约束。之前我们学习的派生和多态,更多的是给我们技能,而封…...

网络安全中大数据和人工智能应用实践
传统的网络安全防护手段主要是通过单点的网络安全设备,随着网络攻击的方式和手段不断的变化,大数据和人工智能技术也在最近十年飞速地发展,网络安全防护也逐渐开始拥抱大数据和人工智能。传统的安全设备和防护手段容易形成数据孤岛࿰…...

RISC-V架构下OP-TEE 安全系统实践
安全之安全(security)博客目录导读 本篇博客,我们聚焦RISC-V 2024中国峰会上的RISC-V和OP-TEE结合的一个安全系统实践,来自芯来科技桂兵老师。 关于RISC-V TEE(可信执行环境)的相关方案,如感兴趣可参考R...

40分钟学 Go 语言高并发:【实战】分布式缓存系统
【实战课程】分布式缓存系统 一、整体架构设计 首先,让我们通过架构图了解分布式缓存系统的整体设计: 核心组件 组件名称功能描述技术选型负载均衡层请求分发、节点选择一致性哈希缓存节点数据存储、过期处理内存存储 持久化同步机制节点间数据同步…...

[创业之路-186]:《华为战略管理法-DSTE实战体系》-1-为什么UTStarcom死了,华为却活了,而且越活越好?
目录 前言 一、市场定位与战略选择 二、技术创新能力 三、企业文化与团队建设 四、应对危机的能力 五、客户为中心的理念 六、市场适应性与战略灵活性 七、技术创新与研发投入 八、企业文化与团队建设 九、应对危机的能力 前言 UT斯达康(UTStarcom&#…...

python如何多行注释
在Python中,多行注释通常有两种方式: 使用三个单引号()或三个双引号(""")来创建多行字符串,这可以被用来作为多行注释。这种方式在Python中实际上是创建了一个多行的字符串对象…...

前端工程化面试题目常见
前端工程化面试常见题目包括: • 谈谈你对WebPack的认识。 • Webpack打包的流程是什么? • 说说你工作中几个常用的loader。 • 说说HtmlWebpackPlugin插件的作用。 • Webpack支持的脚本模块规范有哪些? • Webpack和gulp/grunt相比有什么特…...

定点数的乘除运算
原码一位乘法 乘积的符号由两个数的符号位异或而成。(不参与运算)被乘数和乘数均取绝对值参与运算,看作无符号数。乘数的最低位为Yn: 若Yn1,则部分积加上被乘数|x|,然后逻辑右移一位;若Yn0&…...

页面置换算法模拟 最近最久未使用(LRU)算法
最近最久未使用(LRU)算法是一种基于页面访问历史的页面置换算法。它选择最久未使用的页面进行置换。当需要访问一个不在内存中的页面时,如果内存已满,则选择最久未使用的页面进行置换。LRU算法通过记录页面的访问时间戳来判断页面…...

Ubuntu与Centos系统有何区别?
Ubuntu和CentOS都是基于Linux内核的操作系统,但它们在设计理念、使用场景和技术实现上有显著的区别。以下是详细的对比: 1. 基础和发行版本 Ubuntu: 基于Debian,使用.deb包管理系统。包含两个主要版本: LTSÿ…...

RK3568平台开发系列讲解(pinctrl 子系统篇)pinctrl_debug
🚀返回专栏总目录 文章目录 1. Overview2. debug信息2.1 pinctrl-devices2.2. pinctrl-handles2.3. pinctrl-handles3. debug信息3.1. 查看(pinctrl_register_pins)注册了哪些pins3.2. 查看pin groups;3.3. 查看每种functions所占用的gpio groups信息:3.4. pinconf沉淀、…...

避大坑!Vue3中reactive丢失响应式的问题
在vue3中,我们定义响应式数据无非是ref和reactive。 但是有的小伙伴会踩雷!导致定义的响应式丢失的问题。 reactive丢失响应式的情况1(直接赋值) 场景: 1.你定义了一个数据:let datareactive({name:"",age:"" }) 2.然后你…...

springSecurity权限控制
权限控制:不同的用户可以使用不同的功能。 我们不能在前端判断用户权限来控制显示哪些按钮,因为这样,有人会获取该功能对应的接口,就不需要通过前端,直接发送请求实现功能了。所以需要在后端进行权限判断。࿰…...

Pytorch训练固定随机种子(单卡场景和分布式训练场景)
模型的训练是一个随机过程,固定随机种子可以帮助我们复现实验结果。 接下来介绍一个模型训练过程中固定随机种子的代码,并对每条语句的作用都会进行解释。 def seed_reproducer(seed2333):random.seed(seed)os.environ["PYTHONHASHSEED"] s…...

Conda + JuiceFS :增强 AI 开发环境共享能力
Conda 是当前 AI 应用开发领域中非常流行的环境和包管理系统,因其能够简单便捷地创建与系统资源相隔离的虚拟环境广受欢迎。 Conda 支持在不同的操作系统上重建相同的工作环境,但在环境共享复用方面仍存在一些挑战。比如,在不同机器上复用相…...

人工智能-人机交互的机会
目录 引言HCI领域的发展机会人工智能领域的崛起与机会博雅智信的HCI与AI辅导服务结语 引言 在人类科技不断进步的今天,HCI(人机交互)和人工智能(AI)是两个密切相关且充满潜力的领域。HCI研究如何优化人类与计算机之间…...

【系统架构核心服务设计】使用 Redis ZSET 实现排行榜服务
目录 一、排行榜的应用场景 二、排行榜技术的特点 三、使用Redis ZSET实现排行榜 3.1 引入依赖 3.2 配置Redis连接 3.3 创建实体类(可选) 3.4 编写 Redis 操作服务层 3.5 编写控制器层 3.6 测试 3.6.1 测试 addMovieScore 接口 3.6.2 测试 g…...

elasticsearch基础总结
最近实习,项目用的elasticseatch做的存储库,但是之前对于es接触的不多,查询语法有些不熟,每次想写个DSL查询时都要gpt或者施展搜索大法,所以索性就自己总结总结,以后忘了也方便查。所以这篇文章会持续更新。…...

【慕伏白教程】Zerotier 连接与简单配置
文章目录 下载与安装WindowsLinuxapt安装官方脚本安装 Zerotier 配置新建网络网络配置 终端配置WindowsLinux 下载与安装 Windows 进入Zerotier官方下载网站,点击下载 在下载目录找到安装文件,双击打开后点击 Install 开始安装 安装完成后,…...

Brain.js(九):LSTMTimeStep 实战教程 - 未来短期内的股市指数预测 - 实操要谨慎
系列的前一文RNNTimeStep 实战教程 - 股票价格预测 讲述了如何使用RNN时间序列预测实时的股价, 在这一节中,我们将深入学习如何利用 JavaScript 在浏览器环境下使用 LSTMTimeStep 进行股市指数的短期预测。通过本次实战教程,你将了解到如何用…...

C# 字符串(String)
文章目录 前言创建 String 对象的方式1. 通过给 String 变量指定一个字符串2. 通过使用 String 类构造函数3. 通过使用字符串串联运算符( )4. 通过检索属性或调用一个返回字符串的方法5. 通过格式化方法来转换一个值或对象为它的字符串表示形式 String …...

二进制文件
大多数人听到“二进制”的时候,脑海里可能马上就会联想到电影《黑客帝国》中由“0”和“1”组成的矩阵。 笔者不打算在这里详细讨论二进制的运算、反码、补码之类枯燥的东西,但有几个和开发相关的概念需要做一点澄清和普及。因为这些内容就像空气——用…...

【电子元器件】音频功放种类
本文章是笔者整理的备忘笔记。希望在帮助自己温习避免遗忘的同时,也能帮助其他需要参考的朋友。如有谬误,欢迎大家进行指正。 一、概述 音频功放将小信号的幅值提高至有用电平,同时保留小信号的细节,这称为线性度。放大器的线性…...

linux之vim
一、模式转换命令 vim主要有三种模式:命令模式(Normal Mode)、输入模式(Insert Mode)和底线命令模式(Command-Line Mode)。 从命令模式切换到输入模式:i:在当前光标所在…...

QT的ui界面显示不全问题(适应高分辨率屏幕)
//自动适应高分辨率 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);一、问题 电脑分辨率高,默认情况下,打开QT的ui界面,显示不全按钮内容 二、解决方案 如果自己的电脑分辨率较高,可以尝试以下方案:自…...