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) 失败,部门的删除操作仍然会被提交成功。

这个功能还是挺强大的,不过也需要主要是否会发生数据不一致的问题

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

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

相关文章

git cherry-pick用法

git cherry-pick 如何将我另一个分支上的某个提交合并到新的分支上 首先切换到新分支上git cherry-pick <commit_hash>例如&#xff1a;git cherry-pick f8a70c9

python实现接缝雕刻算法

python实现接缝雕刻算法 接缝雕刻算法步骤详解Python实现详细解释优缺点应用领域接缝雕刻算法(Seam Carving Algorithm)是一种内容感知的图像缩放技术,可以智能地改变图像的尺寸,而不会明显影响其重要内容。它通过动态规划的方式寻找图像中的“接缝”,即在图像中从上到下或…

jenkins自动化持续集成

一、持续集成优势 1.1 解放重复劳动 一次设置&#xff0c;多次复用。持续集成任务可以解放集成、测试、部署等重复性劳动&#xff0c;通过自动化任务能够显著提升集成频率。 1.2 更快解决问题 接入持续集成任务后&#xff0c;能够更早地感知变更后效果&#xff0c;及时进入…

pytest结合allure-pytest插件生成测试报告

目录 一、安装allure-pytest插件 二、下载allure 三、生成allure报告 四、效果展示 一、安装allure-pytest插件 二、下载allure 下载之后解压&#xff0c;解压之后还要配置环境变量&#xff08;把allure目录下bin目录配置到系统变量的path路径&#xff09;&#xff0c;下…

mysql语法介绍

MySQL 语法主要基于 SQL&#xff08;Structured Query Language&#xff09;标准&#xff0c;用于管理和操作关系型数据库。以下是一些基本的 MySQL 语句&#xff1a; 1.创建数据库&#xff1a; CREATE DATABASE database_name; 1.选择数据库&#xff1a; USE database_name;…

WPF使用TouchSocket实现Tcp client

文章目录 前言1、页面展示2、主页面UI代码2、TCP client的UI代码3、Tcp client后台代码实现4、UI与后台代码的关联 前言 在该篇的Demo中&#xff0c;您可以找到以下内容&#xff1a; 1、TouchSocket的使用&#xff1b; 2、CommunityToolkit.Mvvm的使用&#xff1b; 3、AvalonD…

GPT-4o Mini:探索最具成本效益的小模型在软件开发中的应用

随着人工智能技术的迅猛发展&#xff0c;自然语言处理&#xff08;NLP&#xff09;领域也取得了显著的进步。OpenAI 最新发布的 GPT-4o Mini 模型&#xff0c;以其卓越的性能和极具竞争力的价格&#xff0c;成为了广大开发者关注的焦点。作为一名长期关注人工智能及其在软件开发…

破局产品同质化:解锁3D交互式营销新纪元!

近年来&#xff0c;随着数字体验经济的蓬勃发展&#xff0c;3D交互式营销作为一种创新手段迅速崛起&#xff0c;它巧妙地解决了传统产品展示中普遍存在的缺乏差异性和互动性的问题&#xff0c;使您的产品在激烈的市场竞争中独树一帜&#xff0c;脱颖而出。 若您正面临产品营销…

【Python数据结构与算法】枚举----特殊密码锁

题目&#xff1a;特殊密码锁 描述 有一种特殊的二进制密码锁&#xff0c;由n个相连的按钮组成&#xff08;1<n<30&#xff09;&#xff0c;按钮有凹/凸两种状态&#xff0c;用手按按钮会改变其状态。 然而让人头疼的是&#xff0c;当你按一个按钮时&#xff0c;跟它相邻…

自定义协议(应用层协议)——网络版计算机基于TCP传输协议

应用层&#xff1a;自定义网络协议&#xff1a;序列化和反序列化&#xff0c;如果是TCP传输的&#xff1a;还要关心区分报文边界&#xff08;在序列化设计的时候设计好&#xff09;——粘包问题 1、首先想要使用TCP协议传输的网络&#xff0c;服务器和客户端都应该要创建自己…

无损放大图片工具

一、简介 1、Upscayl 是一款无损放大图片工具&#xff0c;支持CPU和GPU&#xff0c;扩图速度特别的快&#xff0c;而且效果特别的好。而且它有多种模型&#xff0c;比如说艺术动漫风格、真实风格、快速生成等等。最大支持16倍放大和亿级别像素&#xff0c;同时支持Windows、Mac…

Ruoyi-WMS本地运行

所需软件 1、JDK&#xff1a;8 安装包&#xff1a;https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.htmlopen in new window 安装文档&#xff1a;https://cloud.tencent.com/developer/article/1698454open in new window 2、Redis 3.0 安装包&a…

Weakly Supervised Contrastive Learning 论文阅读

Abstract 无监督视觉表示学习因对比学习的最新成就而受到计算机视觉领域的广泛关注。现有的大多数对比学习框架采用实例区分作为预设任务&#xff0c;将每个实例视为一个不同的类。然而&#xff0c;这种方法不可避免地会导致类别冲突问题&#xff0c;从而损害所学习表示的质量…

鸿蒙应用框架开发【多HAP】程序框架

多HAP 介绍 本示例展示多HAP开发&#xff0c;简单介绍了多HAP的使用场景&#xff0c;应用包含了一个entry HAP和两个feature HAP&#xff0c;两个feature HAP分别提供了音频和视频播放组件&#xff0c;entry中使用了音频和视频播放组件。 三个模块需要安装三个hap包&#xff…

玩游戏总缺少dll文件怎么办,免费修复DirectX方法

玩游戏或者运行程序时&#xff0c;突然蹦出个提示说“缺少xxxx.dll”&#xff0c;简直让人火大&#xff01;你是不是也遇到过这种情况&#xff0c;重新安装游戏也没用&#xff0c;各种错误提示让人崩溃&#xff1f;别急&#xff0c;阿星今天就来给你支个招&#xff0c;让这烦人…

电子签章-开放签应用

开放签电子签章系统开源工具版旨在将电子签章、电子合同系统开发中的前后端核心技术开源开放&#xff0c;适合有技术能力的个人 / 团队学习或自建电子签章 \ 电子合同功能或应用&#xff0c;避免研发同仁在工作过程中重复造轮子&#xff0c;降低电子签章技术研发要求&#xff0…

Spring源码学习笔记之@Async源码

文章目录 一、简介二、异步任务Async的使用方法2.1、第一步、配置类上加EnableAsync注解2.2、第二步、自定义线程池2.2.1、方法一、不配置自定义线程池使用默认线程池2.2.2、方法二、使用AsyncConfigurer指定线程池2.2.3、方法三、使用自定义的线程池Excutor2.2.4、方法四、使用…

7.25 阿里云OSS上传 + 后台返回token + 导出excel

1.阿里云Oss上传 只需要一点就是上传到云端后&#xff0c;前端调用上传文件接口&#xff0c;返回一个资源路径。 接着在提交表单时&#xff0c;前端把这个路径设置为img的参数即可。 1.1上传限制 只上传图片 Api("阿里云文件管理") CrossOrigin //跨域 RestContr…

算法 定长按组翻转链表

一、题目 已知一个链表的头部head&#xff0c;每k个结点为一组&#xff0c;按组翻转。要求返回翻转后的头部 k是一个正整数&#xff0c;它的值小于等于链表长度。如果节点总数不是k的整数倍&#xff0c;则剩余的结点保留原来的顺序。示例如下&#xff1a; &#xff08;要求不…

谷粒商城实战笔记-60-商品服务-API-品牌管理-效果优化与快速显示开关

文章目录 一&#xff0c;显示状态列改为switch开关二&#xff0c;监听状态改变 首先&#xff0c;把ESLint语法检查关掉&#xff0c;因为这个语法检查过于严格&#xff0c;在控制台输出很多错误信息&#xff0c;干扰开发。 在build目录下下webpack.base.conf.js中&#xff0c;把…