springAOP理解及事务

news2024/11/16 20:34:06

AOP:

springAOP是什么:

AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。

使用场景:

比如你想统计业务中每个方法的执行耗时,那我们最初的想法就是对每个方法一开始写一个获取方法运行开始时间,然后再来一个获取方法运行结束时间
然后相减,那这个做法思路简单,不过问题也很明显,就是如果都每个方法都这样做,我们需要写很多重复的代码,所以AOP就可以解决这样的问题

再比如你想知道谁在业务中调用了增,删,改方法,想将这个调用方法的信息存入到信息日志表中,我们也可以用AOP来解决这样的问题。

springAOP的代理设计思想:

代理模式:

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。  

生活中的代理:

  • 广告商找大明星拍广告需要经过经纪人

  • 合作伙伴找大老板谈合作要约见面时间需要经过秘书

  • 房产中介是买卖双方的代理

  • 太监是大臣和皇上之间的代理 相关术语:

 静态代理:
public class CalculatorStaticProxy implements Calculator {
    
    // 将被代理的目标对象声明为成员变量
    private Calculator target;
    
    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }
    
    @Override
    public 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; //ID
    private Integer operateUser; //操作人ID
    private 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{

    @Autowired
    private HttpServletRequest request;


    @Autowired
    private 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
    @Override
    public void deletedept(Integer id) {
        deptmapper.deletedept(id);//根据id删除部门
        int i = 1/0;
        empMapper.deleteByDeptid(id);//根据部门id删除部门下的员工
    }

通过这个方法可以解决方法中出现RuntimeError,也就是运行时错误
但是,如果你去特意抛一个异常,那还是解决不了,比如下面

@Transactional
    @Override
    public 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)
    @Override
    public 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.coloradmin.cn/o/1956280.html

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

相关文章

基于bert的自动对对联系统

目录 概述 演示效果 核心逻辑 使用方式 1.裁剪数据集 根据自己的需要选择 2.用couplet数据集训练模型 模型存储在model文件夹中 3.将模型转换为ONNX格式 4.打开index.html就可以在前端使用此自动对对联系统了。 本文所涉及所有资源均在传知代码平台可获取。 概述 这个生成器利用…

什么是婚恋聊天交友源码?今天大家讲解一下。源码交付,支持二开,可打包APP小程序H5。

婚恋交友APP开发前景 对于现代的年轻人来说&#xff0c;社恐已经是深入骨子里不可别除的&#xff0c;除了每天上班下班&#xff0c;许多人宁愿宅在家里&#xff0c;面对线下的相亲机构&#xff0c;家里长辈介绍的会都是饭度抗柜的。而这几年疫情的影响更是大大的限制了正常的社…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

7. 运行时数据区-栈

栈的分类 栈分为Java虚拟机栈还有本地方法栈&#xff1a; Java虚拟机栈&#xff1a;用于保存Java中的方法相关的内容本地方法栈&#xff1a;用于保存在Java中使用native 标记的用C来实现方法 由于hotspot的作者发现使用一个栈就可以保存以上两个部分的内容&#xff0c;所以在…

图像生成中图像质量评估指标—PSNR的详细介绍

文章目录 1. 背景介绍2. 实际应用3. 总结和讨论 1. 背景介绍 峰值信噪比&#xff08;Peak Signal-to-Noise Ratio&#xff0c;简称PSNR&#xff09;是一种广泛应用于图像和视频处理领域的客观图像质量评价指标。它主要用于衡量图像的噪声水平和图像质量&#xff0c;可以用来评…

HttpClient初学

介绍&#xff1a; HttpClient 是Apache Jakarta Common 下的子项目&#xff0c;可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包&#xff0c;并且它支持 HTTP 协议最新的版本和建议。 这里阿里云的oss依赖底层是httpclient&#xff0c;所以这里不再重…

用f-string+sys.stdout.write定制“自己的writer”

f-stringsys.stdout.write&#xff0c;在python中“随意”我的输出。 (笔记模板由python脚本于2024年07月29日 08:09:35创建&#xff0c;本篇笔记适合喜欢python并有一定基础的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&am…

dotnet开发编译之争:Ahead-of-Time(AOT) vs Just-in-Time(JIT)谁才是未来最佳编译选择?

1. 前言 编译技术的选择对于现代应用程序的性能至关重要。在.Net开发平台下&#xff0c;选择合适的编译策略对于提升应用程序的响应速度、资源利用率以及最终用户体验有着不可忽视的影响。其中&#xff0c;Ahead-of-Time (AOT) 编译和 Just-in-Time (JIT) 编译是两种广泛采用的…

【编程工具使用技巧】VS如何显示行号

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《编程工具与技巧探索》 期待您的关注 目录 引言 一、VS编译器行号显示的基本步骤 1.打开VS与项目 2.进入选项设置 3.找到并…

Open3D 计算点到平面的距离

目录 一、概述 1.1原理 1.2实现步骤 1.3原理 二、代码实现 1.1关键函数 1.2完整代码 三、实现效果 3.1原始点云 3.2计算距离后赋色的点云 Open3D点云算法汇总及实战案例汇总的目录地址&#xff1a; Open3D点云算法与点云深度学习案例汇总&#xff08;长期更新&#…

【更新2022】省级农业科技活动人员数测算 2009-2022无缺失

省级农业科技活动人员数测算数据在农业经济学、政策研究和农村发展规划等领域的论文研究中具有重要应用价值。首先&#xff0c;这些数据可用于分析省级农业科技活动的规模和结构变化&#xff0c;揭示不同地区在农业科技投入和产出方面的差异&#xff0c;为政府制定农业发展政策…

锅总浅析系统设计

如何进行系统设计&#xff1f;系统设计最佳实践有哪些&#xff1f;系统设计和软件工程有何区别&#xff1f;如何避免过度设计&#xff1f;学习书籍及软件工具推荐有哪些&#xff1f;前后端语言选型有哪些原则&#xff1f;考虑政策因素的系统设计步骤是怎样的&#xff1f; 带着这…

三维推:二维码生成与修改、加logo、设置有效期

进入后台&#xff0c;找到【二维码工具】下的【二维码在线生成】&#xff0c;可以看到&#xff0c;三维推支持网址、图片、音频、视频、文件以及模板生成二维码。 这里&#xff0c;我们以网址生成二维码为例来演示下&#xff0c;如何给二维码加logo、更改颜色等。 首先&#x…

操作系统---进程调度算法相关习题

例题1&#xff1a;某系统采用基于优先权的非抢占式进程调度策略&#xff0c;完成一次进程调度和进程切换的系统时间开销为1us。在T时刻就绪队列中有3个进程P1、P2和P3&#xff0c;其在就绪队列中的等待时间、需要的 CPU 时间和优先权如下表所示。 若优先权值大的进程优先获得CP…

Git基础概念一览:仓库、协议、原理、服务器搭建全解析

Git基础概念详解 一、引言 二、基础概念概览 三、安全通信与协议 四、Git 服务器搭建与操作 五、Git 工作流程与原理 六、Git 日志与操作 七、总结 一、引言 “ 掌握Git的基本概念是高效进行版本控制的第一步。本文将详细介绍如何创建本地和远程仓库&#xff0c;G…

【HarmonyOS4+NEXT】新建项目指南

&#x1f64b;‍ 一日之际在于晨 ⭐本期内容&#xff1a;新建项目指南 &#x1f3c6;系列专栏&#xff1a;鸿蒙HarmonyOS4NEXT&#xff1a;探索未来智能生态新纪元 文章目录 创建项目项目结构概述运行项目Preview预览模拟器运行真机运行 ArkUI框架简介总结 创建项目 点击创建一…

撰写LabVIEW开发方案时,怎么结构清晰、内容详实?

撰写LabVIEW开发方案时&#xff0c;结构清晰、内容详实是至关重要的。以下是一些步骤和技巧&#xff0c;帮助你组织和编写一个高质量的LabVIEW开发方案&#xff1a; 1. 封面 项目名称公司名称日期作者 2. 目录 列出各章节及其页码 3. 摘要 项目概述&#xff1a;简要介绍项…

vue、react前端框架实现TodoList页面案例

原始TodoList网页&#xff08;主要就是链接里网页应用ndex.html、styles.css、script.js &#xff09;&#xff1a; https://blog.csdn.net/weixin_42357472/article/details/140657576 node、npn安装参考&#xff1a; https://blog.csdn.net/weixin_42357472/article/details/…

昇思25天学习打卡营第16天|GAN 图像生成指南:数据集和模型训练手册

目录 MindSpore 环境配置、MNIST 数据集下载及处理展开。 数据集可视化 隐码构造 模型构建 模型训练 效果展示 模型推理 MindSpore 环境配置、MNIST 数据集下载及处理展开。 首先&#xff0c;通过命令行操作安装特定版本的 MindSpore 库&#xff0c;并查看其版本。接着&a…