springAOP理解及事务
AOP:
springAOP是什么:
AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。
使用场景:
比如你想统计业务中每个方法的执行耗时,那我们最初的想法就是对每个方法一开始写一个获取方法运行开始时间,然后再来一个获取方法运行结束时间
然后相减,那这个做法思路简单,不过问题也很明显,就是如果都每个方法都这样做,我们需要写很多重复的代码,所以AOP就可以解决这样的问题
再比如你想知道谁在业务中调用了增,删,改方法,想将这个调用方法的信息存入到信息日志表中,我们也可以用AOP来解决这样的问题。
springAOP的代理设计思想:
代理模式:
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
生活中的代理:
-
广告商找大明星拍广告需要经过经纪人
-
合作伙伴找大老板谈合作要约见面时间需要经过秘书
-
房产中介是买卖双方的代理
-
太监是大臣和皇上之间的代理 相关术语:
静态代理:
public class CalculatorStaticProxy implements Calculator {// 将被代理的目标对象声明为成员变量private Calculator target;public CalculatorStaticProxy(Calculator target) {this.target = target;}@Overridepublic int add(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println("参数是:" + i + "," + j);// 通过目标对象来实现核心业务逻辑int addResult = target.add(i, j);System.out.println("方法内部 result = " + result);return addResult;}
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理
动态代理:
动态代理技术分类
-
JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!(拜把子)
-
cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口!(认干爹) JDK动态代理技术实现(了解)
代理工程:基于jdk代理技术,生成代理对象
AOP的核心概念和执行流程:
连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
目标对象:Target,通知所应用的对象
连接点:
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等
对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint
对于其他四种通知,获取连接点信息只能使用 JoinPoint ,它是 ProceedingJoinPoint 的父类型
JoinPoint的基本方法:
String className = joinPoint.getTarget().getClass().getName(); //获取目标类名
Signature signature = joinPoint.getSignature(); //获取目标方法签名
String methodName = joinPoint.getSignature().getName(); //获取目标方法名
Object[] args = joinPoint.getArgs(); //获取目标方法运行参数
Object res = joinPoint.proceed(); //执行原始方法,获取返回值(环绕通知)(最后一种只有环绕通知能使用)
通知类型:
1:@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行(如果目标方法有异常,那环绕通知的第三格部分结束方法就不会执行)
2:@Before:前置通知,此注解标注的通知方法在目标方法前被执行
3:@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
4:@AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行(这个通知程序必须正常执行才会运行)
5:@AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行(这个只有程序有异常了才会运行)
从上面几种通知就能看出来,第四个和第五个是两个对立的通知。
注意:
@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。(拿不到返回值的话,界面上就不会显示数据)
切入点:
切入点表达式:描述切入点方法的一种表达式
作用:主要用来决定项目中的哪些方法需要加入通知 (Advice)
常见形式:
1:execution(……):根据方法的签名来匹配
2:@annotation(……) :根据注解匹配
切入点表达式的语法:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
例子:execution(public void com.springboottlias.service.impl.DeptServiceImpl.deletedept(java.lang.Integer))
访问修饰符:可省略(比如: public、protected)
切入点表达式中的通配符:* 和 ..
* :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
例子:execution(* com.*.service.*.update*(*))
第一个*表示:任意返回值
第二个*表示:com包下的任意包
第三个*也表示service的包名或类名
第四个*表示方法名中开头是update的方法名,如果是*deptservice这样的,说明以deptservice结尾的方法名
第五个*表示update*方法中任意的一个参数
.. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
例子:execution(* com.itheima..DeptService.*(..))
表示这个函数里面可以有任意个参数。
AOP执行流程:
一旦我们进行了AOP的方法开发,那我们运行得就不是原始对象得方法了,运行得就是一个代理对象
这个代理对象如何理解呢:可以理解为是原始对象方法得加强版(这个加强得意思就是在这个代理对象中多了其它的方法)
当我们想执行原来的原始对象方法,这个时候我们就不是执行原始对象,我们执行的就是这个代理对象。
AOP案例:
将案例中 增、删、改 相关接口的操作日志记录到数据库表中
日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长
1:准备:
这个准备包括引入AOP的依赖,然后建一个日志记录表还有一个对呀日志记录表的实体类
1:在案例工程中引入AOP的起步依赖
<!-- AOP依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>2:导入资料中准备好的数据库表结构,并引入对应的实体类
下面是实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OperateLog {private Integer id; //IDprivate Integer operateUser; //操作人IDprivate LocalDateTime operateTime; //操作时间private String className; //操作类名private String methodName; //操作方法名private String methodParams; //操作方法参数private String returnValue; //操作方法返回值private Long costTime; //操作耗时
}Sql脚本:
-- 操作日志表
create table operate_log(id int unsigned primary key auto_increment comment 'ID',operate_user int unsigned comment '操作人ID',operate_time datetime comment '操作时间',class_name varchar(100) comment '操作的类名',method_name varchar(100) comment '操作的方法名',method_params varchar(1000) comment '方法参数',return_value varchar(2000) comment '返回值',cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';
2:自定义注解 @Log:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
3:定义切面类,完成记录操作日志的逻辑
@Slf4j
@Component//将这个类交给IOC容器管理
@Aspect//声明这个类是一个AOP类
public class OperateLogAspect{@Autowiredprivate HttpServletRequest request;@Autowiredprivate OperateLogMapper operateLogMapper;@Pointcut("@annotation(com.springboottlias.anno.Log)")private void pt(){}@Around("pt()")public Object RecordtoDatebase(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {//操作人ID - 当前登录员工ID//获取请求头中的jwt令牌,解析令牌String jwt = request.getHeader("token");Claims claims = JwtUtils.parseJWT(jwt);Integer operateUserId = (Integer) claims.get("id");//操作时间LocalDateTime operateTime = LocalDateTime.now();//操作类名String classname = proceedingJoinPoint.getTarget().getClass().getName();//操作方法名String methodname = proceedingJoinPoint.getSignature().getName();//操作方法参数Object[] args = proceedingJoinPoint.getArgs();String methodParams = Arrays.toString(args);long begin = System.currentTimeMillis();//原始方法执行之前的时间//调用原始目标方法运行Object res = proceedingJoinPoint.proceed();//方法返回值String returnValue = JSONObject.toJSONString(res);//操作耗时long end = System.currentTimeMillis();//原始方法执行之后的时间long costtime = end-begin;//记录操作日志OperateLog operateLog = new OperateLog(null,operateUserId,operateTime,classname,methodname,methodParams,returnValue,costtime);operateLogMapper.insert(operateLog);log.info("AOP操作日志:{}",operateLog);return res;}
}
这一步有几个注意点:
1:你想要获得操作人的ID,就得用到JWT令牌功能:你得先从请求头中获得jwt令牌,并且解析token
现在的问题就转化为了,你怎么得到这个令牌,我们知道令牌是在请求头中的,请求头的对象是Request
所以,我们可以从IOC容器中获得Request对象,然后获取令牌。
@Autowired
private HttpServletRequest request;
//操作人ID - 当前登录员工ID
//获取请求头中的jwt令牌,解析令牌
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUserId = (Integer) claims.get("id");
2:第二个处理的点就是这个方法返回值这边的处理:
用到了之前的一个工具类,将对象转化成json格式的数据。
4:在需要的方法上添加注解@Log
事务:
简单理解:事务就是不可拆分的最小事件
Spring事务管理(底层是AOP)
位置:业务(service)层的方法上、类上、接口上
作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
@Transactional这个注解可以作用在方法,接口和类上,不过我们一般作用在业务层的增删改业务上,也就是需要访问多次数据访问的方法上。
@Transactional@Overridepublic void deletedept(Integer id) {deptmapper.deletedept(id);//根据id删除部门int i = 1/0;empMapper.deleteByDeptid(id);//根据部门id删除部门下的员工}
通过这个方法可以解决方法中出现RuntimeError,也就是运行时错误
但是,如果你去特意抛一个异常,那还是解决不了,比如下面
@Transactional@Overridepublic void deletedept(Integer id) throws Exception {deptmapper.deletedept(id);//根据id删除部门if(true){throw new Exception("错误");}empMapper.deleteByDeptid(id);//根据部门id删除部门下的员工}
这个时候就需要用到spring事务中的一个回滚事务注释@Transactional(rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)@Overridepublic void deletedept(Integer id) throws Exception {deptmapper.deletedept(id);//根据id删除部门if(true){throw new Exception("错误");}empMapper.deleteByDeptid(id);//根据部门id删除部门下的员工}
这样处理之后,所有的异常都会回滚
事务属性-传播行为:
事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
设想一个场景:在一个事务里,又开启了另一个事务,那这个时候,这两个事务的回滚或者叫传播行为是怎么样的呢
这个注解可以解释@Transactional(propagation = Propagation.REQUIRED)
REQUIRED 【默认值】需要事务,有则加入,无则创建新事务
REQUIRES_NEW 需要新事务,无论有无,总是创建新事务
当这个注解的值是这个的时候,就说明,两个事务时绑定在一起的,比如在delete这个方法中又新建了一个事务,然后delete方法因为
异常rollback了,那这个事务也得rollback。
但如果你设置的值是这个:@Transactional(propagation = Propagation.REQUIRES_NEW)
那这个时候,就会新开一个事务,就算上面的delete方法rollback了,你这个事务还是会接着执行。
propagation = Propagation.REQUIRES_NEW:比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
举个例子:
@Transactional
public void removeDepartmentAndEmployees(Integer deptId) { deletedept(deptId); // 调用配置了 REQUIRES_NEW 的方法
} @Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void deletedept(Integer id) { deptMapper.deletedept(id); // 如果这条操作成功 empMapper.deleteByDeptid(id); // 假设这条操作失败
}
在上面的例子中,假设 deletedept
方法中的 deptMapper.deletedept(id)
调用成功,但 empMapper.deleteByDeptid(id)
调用失败:
如果 deletedept
使用了 REQUIRES_NEW
,则 deptMapper.deletedept(id)
将成功提交,即使后面的 empMapper.deleteByDeptid(id)
失败,部门的删除操作仍然会被提交成功。
这个功能还是挺强大的,不过也需要主要是否会发生数据不一致的问题
相关文章:

springAOP理解及事务
AOP: springAOP是什么: AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。 使用场景: 比如你想统计业务中每个方法的执行耗时,那我们最…...

Optional类的使用 java8(附代码)
🍰 个人主页:_小白不加班__ 🍞文章有不合理的地方请各位大佬指正。 🍉文章不定期持续更新,如果我的文章对你有帮助➡️ 关注🙏🏻 点赞👍 收藏⭐️ 文章目录 一、什么是Optional?二、…...

企业利用AI智能名片S2B2C商城小程序参与社区团购的风险与机遇分析
摘要 在新零售浪潮的推动下,社区团购以其独特的商业模式迅速崛起,成为连接消费者与供应商的重要桥梁。企业纷纷探索如何有效利用这一新兴渠道,以扩大市场份额、提升品牌影响力。AI智能名片S2B2C商城小程序的引入,为企业参与社区团…...

全链路追踪 性能监控,GO 应用可观测全面升级
作者:古琦 01 介绍 随着 Kubernetes 和容器化技术的普及,Go 语言不仅在云原生基础组件领域广泛应用,也在各类业务场景中占据了重要地位。如今,越来越多的新兴业务选择 Golang 作为首选编程语言。得益于丰富的 RPC 框架ÿ…...
深入探索CSS3的Media Query:打造响应式网页设计的利器
在今天的互联网世界中,随着设备种类和屏幕尺寸的多样化,响应式网页设计(Responsive Web Design, RWD)已成为不可或缺的一部分。CSS3中的Media Query正是这一设计理念的实现利器,它允许开发者根据用户的设备特性和屏幕尺…...
DDD(Domain-Driven Design)领域驱动设计
在软件开发中,DDD(Domain-Driven Design,领域驱动设计)是一种方法论,它强调在开发过程中将业务领域的知识和规则作为核心。DDD的目标是通过理解和建模业务领域来创建更好的软件系统。本文将详细讲解DDD的基本概念、原则…...

基于k8s快速搭建docker镜像服务的demo
基于k8s快速搭建docker镜像服务的demo 一、环境准备 如标题,你需要环境中有和2个平台,并且服务器上也已经安装好docker服务 接下来我来构建一个docker镜像,然后使用harbork8s来快速部署服务demo 二、部署概述 使用docker构建镜像&#x…...

“论大数据处理架构及其应用”写作框架,软考高级论文,系统架构设计师论文
论文真题 大数据处理架构是专门用于处理和分析巨量复杂数据集的软件架构。它通常包括数据收集、存储、处理、分析和可视化等多个层面,旨在从海量、多样化的数据中提取有价值的信息。Lambda架构是大数据平台里最成熟、最稳定的架构,它是一种将批处理和流…...

tarojs项目启动篇
TaroJS 是一个开放式跨端开发解决方案,使用 React 语法规范来开发多端应用(包括小程序、H5、React Native 等)。它可以帮助开发者高效地构建出在不同端上运行一致的应用。以下是启动 TaroJS 项目(本来就有的旧项目)的步…...
Maven打包时将本地 jar 加入 classpath
在使用 maven 编译项目时,我们有时候会需要引入本地的 jar 包作为依赖(不部署到本地仓库),一般会使用 scope 为 system 的标签来引入,如下所示: <dependency><groupId>com.example</groupI…...
Spring Boot打出的jar包为什么可以独立运行
闲来无事,浏览网页看到有人说jar包为什么可以独立运行,想起前端时间写的jar打包后无法正常运行处理。jar解压缩后有多个文件夹,内部存放运行所需jar包和一些配置文件,本文做一个简单介绍。 JAR包和WAR包区别 在Java开发中&#…...

“微软蓝屏”事件:网络安全与稳定性的深刻反思
🌈所属专栏:【其它】✨作者主页: Mr.Zwq✔️个人简介:一个正在努力学技术的Python领域创作者,擅长爬虫,逆向,全栈方向,专注基础和实战分享,欢迎咨询! 您的点…...

【技术升级】Docker环境下Nacos平滑升级攻略,安全配置一步到位
目前项目当中使用的Nacos版本为2.0.2,该版本可能存在一定的安全风险。软件的安全性是一个持续关注的问题,尤其是对于像Nacos这样的服务发现与配置管理平台,它在微服务架构中扮演着核心角色。随着新版本的发布,开发团队会修复已知的…...

[题解]CF1401E.Divide Square(codeforces 05)
题目描述 There is a square of size 106106106106 on the coordinate plane with four points (0,0)(0,0) , (0,106)(0,106) , (106,0)(106,0) , and (106,106)(106,106) as its vertices. You are going to draw segments on the plane. All segments are either horizonta…...
软考高级第四版备考--第32天(新一代信息技术及应用)
1、物联网 1.1技术基础 1.1.1感知层:由各种传感器构成,包括温度传感器,二维码标签、RFID标签和读写器,摄像头,GPS等感知终端。感知层是物联网识别物体、采集信息的来源。 1.1.2网络层:由各种网络&#x…...

【RabbitMQ】MQ相关概念
一、MQ的基本概念 定义:MQ全称为Message Queue,是一种提供消息队列服务的中间件,也称为消息中间件。它允许应用程序通过读写队列中的消息来进行通信,而无需建立直接的连接。作用:主要用于分布式系统之间的通信&#x…...

【MySQL是怎样运行的 | 第二篇】MySQL三大日志文件
文章目录 2.MySQL三大日志文件2.1日志文件列表2.1.1 redo log2.1.2 bin log2.1.3 undo log 2.2redo log日志详讲2.3 binglog和redo log有什么区别?2.4一条更新语句的执行过程 2.MySQL三大日志文件 2.1日志文件列表 redo log:重做日志,记录了…...

视图、存储过程、触发器
一、视图 视图是从一个或者几个基本表(或视图)导出的表。它与基 本表不同,是一个虚表,视图只能用来从查询,不能做增删改(虚拟的表) 1.创建视图 创建视图的语法: create view 视图名【view_xxx / v_xxx】 a…...

【学习笔记】解决Serial Communication Library编译问题
【学习笔记】解决编译 Serial Communication Library 时的 Catkin 依赖问题 Serial Communication Library 是一个用 C 编写的用于连接类似 rs-232 串口的跨平台库。它提供了一个现代的 C 接口,它的工作流程设计在外观和感觉上与 PySerial 相似,但串口速…...
在 Windows 环境下实现负载均衡:提升系统性能与可靠性的关键技术
Windows 环境下的负载均衡:提升系统性能与可靠性的关键技术 负载均衡(Load Balancing)是现代网络架构中不可或缺的一部分,通过将请求分配到多台服务器上来提高系统的性能和可靠性。本文将介绍在 Windows 环境下使用负载均衡的基本…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...