【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 支持在不同的操作系统上重建相同的工作环境,但在环境共享复用方面仍存在一些挑战。比如,在不同机器上复用相…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...