AOP基础、快速入门、进阶
一、概述
AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程
那什么又是面向方法编程呢,为什么又需要面向方法编程呢?来我们举个例子做一个说明:
比如,我们这里有一个项目,项目中开发了很多的业务功能。
场景:
案例部分功能运行较慢,我们需要定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时
此时我们就需要统计当前这个项目当中每一个业务方法的执行耗时。那么统计每一个业务方法的执行耗时该怎么实现?
可能多数人首先想到的就是在每一个业务方法运行之前,记录这个方法运行的开始时间。在这个方法运行完毕之后,再来记录这个方法运行的结束时间。拿结束时间减去开始时间,不就是这个方法的执行耗时吗?
而AOP面向方法编程,就可以做到在不改动这些原始方法的基础上,针对特定的方法进行功能的增强。
AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)
那面向这样的指定的一个或多个方法进行编程,我们就称之为 面向切面编程。
二、快速入门
- 导入依赖:在pom.xml中导入AOP的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 编写AOP程序:针对于特定方法根据业务需要进行编程
@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;}
}
三、核心概念
连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
连接点指的是可以被aop控制的方法。例如:入门程序当中所有的业务方法都是可以被aop控制的方法。
通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。
切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
在通知当中,我们所定义的共性功能到底要应用在哪些方法上?此时就涉及到了切入点pointcut概念。切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。在aop的开发当中,我们通常会通过一个切入点表达式来描述切入点(后面会有详解)。
切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
当通知和切入点结合在一起,就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法,在什么时候执行什么样的操作。
目标对象:Target,通知所应用的对象
四、进阶
4.1. 通知类型
-
@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
-
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
-
@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
-
@AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
-
@AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行
下面我们通过代码演示,来加深对于不同通知类型的理解:
@Slf4j
@Component
@Aspect
public class MyAspect1 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(JoinPoint joinPoint){log.info("before ...");}//环绕通知@Around("execution(* com.itheima.service.*.*(..))")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {log.info("around before ...");//调用目标对象的原始方法执行Object result = proceedingJoinPoint.proceed();//原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了log.info("around after ...");return result;}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(JoinPoint joinPoint){log.info("after ...");}//返回后通知(程序在正常执行的情况下,会执行的后置通知)@AfterReturning("execution(* com.itheima.service.*.*(..))")public void afterReturning(JoinPoint joinPoint){log.info("afterReturning ...");}//异常通知(程序在出现异常的情况下,执行的后置通知)@AfterThrowing("execution(* com.itheima.service.*.*(..))")public void afterThrowing(JoinPoint joinPoint){log.info("afterThrowing ...");}
}
在使用通知时的注意事项:
@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。
另外:
Spring提供了@PointCut注解,该注解的作用是将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可。
@Slf4j
@Component
@Aspect
public class MyAspect1 {//切入点方法(公共的切入点表达式)@Pointcut("execution(* com.itheima.service.*.*(..))")private void pt(){}//前置通知(引用切入点)@Before("pt()")public void before(JoinPoint joinPoint){log.info("before ...");}//环绕通知@Around("pt()")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {log.info("around before ...");//调用目标对象的原始方法执行Object result = proceedingJoinPoint.proceed();//原始方法在执行时:发生异常//后续代码不在执行log.info("around after ...");return result;}//后置通知@After("pt()")public void after(JoinPoint joinPoint){log.info("after ...");}//返回后通知(程序在正常执行的情况下,会执行的后置通知)@AfterReturning("pt()")public void afterReturning(JoinPoint joinPoint){log.info("afterReturning ...");}//异常通知(程序在出现异常的情况下,执行的后置通知)@AfterThrowing("pt()")public void afterThrowing(JoinPoint joinPoint){log.info("afterThrowing ...");}
}
需要注意的是:当切入点方法使用private修饰时,仅能在当前切面类中引用该表达式, 当外部其他切面类中也要引用当前类中的切入点表达式,就需要把private改为public,而在引用的时候,具体的语法为:全类名.方法名()
4.2. 通知顺序
当在项目开发当中,我们定义了多个切面类,而多个切面类中多个切入点都匹配到了同一个目标方法。此时当目标方法在运行的时候,这多个切面类当中的这些通知方法都会运行。
此时我们就有一个疑问,这多个通知方法到底哪个先运行,哪个后运行?
在不同切面类中,默认按照切面类的类名字母排序:
-
目标方法前的通知方法:字母排名靠前的先执行
-
目标方法后的通知方法:字母排名靠前的后执行
如果我们想控制通知的执行顺序有两种方式:
-
修改切面类的类名(这种方式非常繁琐、而且不便管理)
-
使用Spring提供的@Order注解
使用@Order注解,控制通知的执行顺序:
@Slf4j
@Component
@Aspect
@Order(2) //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
public class MyAspect2 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect2 -> before ...");}//后置通知 @After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect2 -> after ...");}
}
@Slf4j
@Component
@Aspect
@Order(3) //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
public class MyAspect3 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect3 -> before ...");}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect3 -> after ...");}
}
@Slf4j
@Component
@Aspect
@Order(1) //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
public class MyAspect4 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect4 -> before ...");}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect4 -> after ...");}
}
4.3. 切入点表达式
4.3.1 execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
其中带?
的表示可以省略的部分
-
访问修饰符:可省略(比如: public、protected)
-
包名.类名: 可省略
-
throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
示例:
@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
可以使用通配符描述切入点
-
*
:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分 -
..
:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
切入点表达式的语法规则:
1. 方法的访问修饰符可以省略
2. 返回值可以使用`*`号代替(任意返回值类型)
3. 包名可以使用`*`号代替,代表任意包(一层包使用一个`*`)
4. 使用`..`配置包名,标识此包以及此包下的所有子包
5. 类名可以使用`*`号代替,标识任意类
6. 方法名可以使用`*`号代替,表示任意方法
7. 可以使用 `*` 配置参数,一个任意类型的参数
8. 可以使用`..` 配置参数,任意个任意类型的参数
切入点表达式示例
注意事项:
- 根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。
4.2.2 @annotation
已经学习了execution切入点表达式的语法。那么如果我们要匹配多个无规则的方法,比如:list()和 delete()这两个方法。这个时候我们基于execution这种切入点表达式来描述就不是很方便了。而在之前我们是将两个切入点表达式组合在了一起完成的需求,这个是比较繁琐的。
我们可以借助于另一种切入点表达式annotation来描述这一类的切入点,从而来简化切入点表达式的书写。(注解的全类名)
实现步骤:
1. 编写自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
2. 在业务类要做为连接点的方法上添加自定义注解
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Override@MyLog //自定义注解(表示:当前方法属于目标方法)public List<Dept> list() {List<Dept> deptList = deptMapper.list();//模拟异常//int num = 10/0;return deptList;}@Override@MyLog //自定义注解(表示:当前方法属于目标方法)public void delete(Integer id) {//1. 删除部门deptMapper.delete(id);}@Overridepublic void save(Dept dept) {dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.save(dept);}@Overridepublic Dept getById(Integer id) {return deptMapper.getById(id);}@Overridepublic void update(Dept dept) {dept.setUpdateTime(LocalDateTime.now());deptMapper.update(dept);}
}
3.切面类
@Slf4j
@Component
@Aspect
public class MyAspect6 {//针对list方法、delete方法进行前置通知和后置通知//前置通知@Before("@annotation(com.itheima.anno.MyLog)")public void before(){log.info("MyAspect6 -> before ...");}//后置通知@After("@annotation(com.itheima.anno.MyLog)")public void after(){log.info("MyAspect6 -> after ...");}
}
4.4. 连接点
讲解完了切入点表达式之后,接下来我们再来讲解最后一个部分连接点。我们前面在讲解AOP核心概念的时候,我们提到过什么是连接点,连接点可以简单理解为可以被AOP控制的方法。
我们目标对象当中所有的方法是不是都是可以被AOP控制的方法。而在SpringAOP当中,连接点又特指方法的执行。
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
- 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型
- 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型
示例代码:
@Slf4j
@Component
@Aspect
public class MyAspect7 {@Pointcut("@annotation(com.itheima.anno.MyLog)")private void pt(){}//前置通知@Before("pt()")public void before(JoinPoint joinPoint){log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");}//后置通知@Before("pt()")public void after(JoinPoint joinPoint){log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");}//环绕通知@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable {//获取目标类名String name = pjp.getTarget().getClass().getName();log.info("目标类名:{}",name);//目标方法名String methodName = pjp.getSignature().getName();log.info("目标方法名:{}",methodName);//获取方法执行时需要的参数Object[] args = pjp.getArgs();log.info("目标方法参数:{}", Arrays.toString(args));//执行原始方法Object returnValue = pjp.proceed();return returnValue;}
}
相关文章:

AOP基础、快速入门、进阶
一、概述 AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程 那什么又是面向方法编程呢,为什么又需要面向方法编程呢?来我们举个例子做一个说明: 比如…...

哪款宠物空净运行吸毛好、噪音小?希喂、霍尼韦尔、安德迈测评!
作为宠物领域目前最火热的产品,宠物空气净化器的讨论度一直很高。身为铲屎官的我在产品刚出的时候就购入了一台,结果让我非常失望! 抛开产品效果不提,它运行起来的声音实在太大了!我家猫根本不愿意靠近,每…...

新兴的安全职业挑战
我们经常与安全专业人士交谈,他们希望在努力提升职业发展的同时提高自己的价值并克服组织内部的挑战。在这些谈话中,花费大量时间讨论公司未来将面临的安全问题并不罕见。 安全领导者希望为问题制定计划并获得领导层对其计划的支持。这通常意味着实施修…...

代码随想录算法训练营Day32 | 122.买卖股票的最佳时机Ⅱ、55.跳跃游戏、45.跳跃游戏Ⅱ、1005.K次取反后最大化的数组和
目录 122.买卖股票的最佳时机Ⅱ 55.跳跃游戏 45.跳跃游戏Ⅱ 1005.K次取反后最大化的数组和 122.买卖股票的最佳时机Ⅱ 题目 122. 买卖股票的最佳时机 II - 力扣(LeetCode) 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i…...

3D Slicer 教程一
先了解一下什么是3D Slicer,这个是做什么,然后一步步了解功能,一起看看源码 一.初识 这块软件用来处理医学影像,是一款开源的软件. 里面涉及到一些 2d 常见的操作,图像处理,调窗,测量; 涉及到3d的一些常见重建,mpr,vr,cpr等, 还包括一些分割,变换等(越高级精确的一些通过插件…...

github pages + hugo 搭建静态博客网站
体验地址 1. 起因, 目的: 其实6年前,我就写过这个。 项目代码 博客地址 最近想改写一下。 github 推荐的主题是 Jekyll, 我当时用的就是这个,感觉很麻烦。尤其是文章命名。 新的主题 hugo 用起来还行。 2.过程: 过程记录&am…...

Python爬虫如何爬取并解析JSON数据
前言 Python爬虫是一种用于从互联网上获取数据的程序,而JSON(JavaScript Object Notation)是一种常用的数据交换格式。本文将介绍如何使用Python爬虫来爬取并解析JSON数据,同时还会讲解如何使用代理IP来提高爬取效率。 1. 什么是…...

【C++】精妙的哈希算法
🚀个人主页:小羊 🚀所属专栏:C 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 一、哈希结构1、哈希概念2、哈希函数3、哈希冲突3.1 闭散列3.2 开散列 4、完整代码 一、哈希结构 1、哈希概念 A…...

智慧链动青春:国家区块链中心接待北京市十一学校青少年访学探索
以生动科学的方法点燃青少年科学探索欲望是构建未来科技人才梯队的基石。近期国家区块链技术创新中心接待北京市十一学校新生访学,以科普讲座、实操互动的方式让学生在深度思考中感受科学魅力、接触前沿科技,激发学生对区块链、隐私计算和芯片设计制造的…...

利用C++封装鼠标轨迹算法为DLL:游戏行为检测的利器
在现代软件开发中,鼠标轨迹模拟技术因其在自动化测试、游戏脚本编写等领域的广泛应用而备受青睐。本文将介绍如何使用C语言将鼠标轨迹算法封装为DLL(动态链接库),以便在多种编程环境中实现高效调用,同时探讨其在游戏行…...

Qt- QSS风格选择器常用属性选择器样式表盒子
1. 风格设置 Qt 提供了 3 种整体风格,使用 QStyleFactory::keys() 来获取 (windowsvista 、Windows 、Fusion) 可以在 main.cpp 中调用 setStyle 方法对应用程序进行全局风格的设置 int main(int argc, char *argv[]) {QApplication a(arg…...

粤智助自助一体机大厂浮出水面 OBOO鸥柏已成服务终端中坚力量
自助服务查询一体机作为操作自主化便民的重要窗口,OBOO鸥柏自助服务终端机以其显著的技术优化,通过触摸屏或其他交互界面,使用户能够自助服务完成各种操作,如支付、查询信息终端、办理业务,自助查档答应一体化等。为交…...
SpringBoot-application.properties配置
默认配置最终都是映射/关联到某个类 #SPRING CONFIG(ConfigFileApplicationListener) spring.config.name #配置文件名(默认 为 application ) spring.config.location #配置文件的位置 …...

STM32-ADC模数转换
一、概述 ADC(Analog-Digital Converter)模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁12位逐次逼近型ADC,1us转换时间输入电压范围:0~3.3Vÿ…...
lspci | grep VGA
执行lspci | grep VGA后如下,解释含义 00:0f.0 VGA compatible controller: VMware SVGA II Adapter 0b:00.0 VGA compatible controller: NVIDIA Corporation GA104 [GeForce RTX 3070] (rev a1) 执行 lspci | grep VGA 命令后,您得到了两条输出&#…...

智慧厂区车辆导航解决方案;智慧工厂电子地图应用解决方案;大型工厂内部导航解决方案;智慧工厂可视化地图应用方案
智慧厂区车辆导航解决方案;智慧工厂电子地图应用解决方案 在智慧工业的蓬勃发展背景下,上海懒图科技凭借其室内电子地图技术的深厚积淀,正为智慧工厂物流管理领域注入革新力量。其创新的车辆导航与可视化管理系统,凭借高精度定位…...
决策树C4.5算法详解及实现
C4.5决策树是一种广泛使用的机器学习算法,它用于分类任务。它是在ID3算法的基础上改进的,主要通过生成决策树来构建分类模型。C4.5通过以下步骤工作: 1. 数据集分裂 C4.5通过选择具有最高信息增益率的特征来分裂数据集。信息增益率…...

prompt learning
prompt learning 对于CLIP(如上图所示)而言,对其prompt构造的更改就是在zero shot应用到下游任务的时候对其输入的label text进行一定的更改,比如将“A photo of a{obj}”改为“[V1][V2]…[Vn][Class]”这样可学习的V1-Vn的token…...

适用于 Windows 11 的 5 大数据恢复软件 [免费和付费]
为什么我们需要Windows 11数据恢复软件? 计算机用户经常遇到的一件事就是数据丢失,这种情况随时可能发生。错误地删除重要文件和文件夹可能会非常令人担忧,但幸运的是,有一种方法可以恢复 PC 上丢失的数据。本文将向您展示可用于…...
vue实现获取当前时间并实时显示
以下代码可以实现获取当前时间并实时显示,朋友们直接copy使用即可,希望可以帮助到有需要的朋友们! <template><div class"time">{{ datetimeStr }}</div> </template> <script>export default {data…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...