简单模拟 Spring 创建的动态代理类(解释一种@Transactional事务失效的场景)

模拟 Spring 创建的动态代理类

本文主要目的是从父类和子类继承的角度去分析为什么在 @Service 标注的业务类中使用 this 调用方法会造成事务失效。解释在这种情况下 this 为什么是原始类对象而不是代理类对象。

问题描述

在 @Service 标注的业务类中,如果调用本类中的方法,那么会造成事务失效。原因是因为事务的功能是 @Transactional 注解通过 AOP 切面的方式对原始类进行的增强,因此事务功能是代理类对象中的方法才具备的。

现在问题来了,在 CGLib 的动态代理模式中,代理类(假设为 UserServiceImplProxy)是继承了 UserServiceImpl,也就是说代理类是原始类的子类,而通过 Spring 容器的 getBean 方法获取到的也是代理类对象,那么在主方法中调用 userServiceImplProxy.transactionFailTest() 方法,那问题似乎变成了在父类中使用 this 关键字时,this 代表的是子类对象还是父类对象?

先说结论,this 代表的对象是不确定的。

@Service
public class UserServiceImpl {@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserServiceImpl userServiceImpl;public void transactionFailTest() {System.out.println("this=" + this);System.out.println("this.getClass()=" + this.getClass());System.out.println("this.getClass().getSuperclass() = " + this.getClass().getSuperclass());// 重点是探究this对象到底是什么?为什么this不是代理类对象this.transactionTest();}public void transactionSuccessTest() {// 调用代理类中的方法userServiceImpl.transactionTest();}@Transactionalpublic void transactionTest() {userMapper.updatePasswordById(1L, "111111");// if (true) {//     throw new RuntimeException("故意制造异常");// }userMapper.updatePasswordById(2L, "222222");}
}

继承关系中的方法调用

在下面的测试案例中,同样是在父类 Parent 中的方法中使用 this 关键字,而实际调用的是子类 Child 中的方法。这是因为 main 方法中方法的调用者就是一个 Child 对象,所以无论是 Parent 类还是 Child 类中的 this,都是指向该调用对象的地址。

/*** 通过super调用父类方法*/
@Slf4j
public class SuperCallMainDemo {public static void main(String[] args) {Parent parent = new Child();log.error("main方法中的调用者对象={}", parent);parent.method01();}static class Child extends Parent {@Overridepublic void method01() {log.info("******************************************");super.method01();log.info("******************************************");}@Overridepublic void method02() {log.info("==========================================");super.method02();log.info("==========================================");}}static class Parent {public void method01() {log.info("Parent执行method01方法, this={}", this);this.method02();}public void method02() {log.info("Parent执行method02方法, this={}", this);}}
}

image-20231120174350253

在继承中使用反射进行方法调用(模拟动态代理类逻辑)

在下面的测试案例中,和 Spring 通过 CGLIB 动态代理生成的动态代理类的原理相同。虽然代理类是子类,但由于是动态生成的,所以没有办法通过 super 关键字来直接调用父类中的同名方法,因此即使拦截到父类中的方法 m1、m2,也还是需要通过 invoke 反射的方式进行调用。因此 this 关键字指向的是 invoke 方法传递过去的父类对象。

/*** 通过反射调用父类方法*/
@Slf4j
public class InvokeCallMainDemo {public static void main(String[] args) {Parent parent = new Parent();log.error("main方法中parent的地址={}", parent);Parent child = new Child(parent, Parent.class);log.error("main方法中child的地址={}", child);child.method01();}static class Child extends Parent {Parent target;Class<?> clazz;Method m1;Method m2;@SneakyThrowspublic Child(Parent target, Class<?> clazz) {this.target = target;this.clazz = clazz;// 这里模拟代理类拦截父类的所有方法m1 = clazz.getMethod("method01");m2 = clazz.getMethod("method02");}@SneakyThrows@Overridepublic void method01() {log.info("******************************************");// 实际上这里的方法是被拦截下来的m1.invoke(target);log.info("******************************************");}@SneakyThrows@Overridepublic void method02() {log.info("==========================================");m2.invoke(target);log.info("==========================================");}}static class Parent {public void method01() {log.info("Parent执行method01方法, this={}", this);this.method02();}public void method02() {log.info("Parent执行method02方法, this={}", this);}}
}

image-20231120175943952

总结

  • 无论是那种调用方式,this 都表示实际调用的那个对象,不会因为使用 super 关键字而被更改。
  • 在反射调用方式中,通过 method.invoke(target) 进行调用方法时,传递的对象就是 target,因此 this 表示的就是 target 对象。(动态代理类只能选择这种方式)
  • Spring 中的代理类会保存原始类对象,通过反射的方式去调用原始类中的方法。这里通过模拟的方式实际上代理类中除了继承隐式地保存一个原始类对象之外,还显式地保存了一个原始类对象,因为 super 并不能够和 this 一样可以独立作为一个对象引用来使用。

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

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

相关文章

YOLOv8 加持 MobileNetv3,目标检测新篇章

🗝️YOLOv8实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局部注意力、增加检测头部,实测涨点 💡 深入浅出YOLOv8:我的专业笔记与技术总结   -- YOLOv8轻松上手, 适用技术小白,文章代码齐全,仅需 …

【狂神说Java】redis入门

✅作者简介&#xff1a;CSDN内容合伙人、信息安全专业在校大学生&#x1f3c6; &#x1f525;系列专栏 &#xff1a;【狂神说Java】 &#x1f4c3;新人博主 &#xff1a;欢迎点赞收藏关注&#xff0c;会回访&#xff01; &#x1f4ac;舞台再大&#xff0c;你不上台&#xff0c…

卷?中学生开始学习人工智能和大模型,附课件!

卷&#xff1f;中学生开始学习人工智能和大模型&#xff0c;附课件&#xff01; 大家好&#xff0c;我是老章 发现一个面向11-14岁人群的AI课程&#xff0c;还附加了大模型内容&#xff0c;浏览了一遍它们的课件&#xff08;还有面向教师的资源&#xff09;&#xff0c;感觉非…

Linux——编译器gcc/g++、调试器gdb以及自动化构建工具makefilemake详解

编译器—gcc/g、调试器—gdb以及自动化构建工具—makefile&&make 文章目录 编译器—gcc/g、调试器—gdb以及自动化构建工具—makefile&&make1. 编译器——gcc/g1.1 生成可执行文件与修改默认可执行文件1.2 程序的翻译过程以及对应的gcc选项1.2.1 预处理 gcc -E…

【cpolar】TortoiseSVN如何安装并实现公网提交文件到本地SVN服务器

&#x1f3a5; 个人主页&#xff1a;深鱼~ &#x1f525;收录专栏&#xff1a;cpolar &#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控…

RabbitMQ 部署及配置详解(集群部署)

单机部署请移步&#xff1a; RabbitMQ 部署及配置详解 (单机) RabbitMQ 集群是一个或 多个节点&#xff0c;每个节点共享用户、虚拟主机、 队列、交换、绑定、运行时参数和其他分布式状态。 一、RabbitMQ 集群可以通过多种方式形成&#xff1a; 通过在配置文件中列出群集节点以…

JVM的运行时数据区

Java虚拟机&#xff08;JVM&#xff09;的运行时数据区是程序在运行过程中使用的内存区域&#xff0c;主要包括以下几个部分&#xff1a; 程序计数器虚拟机栈本地方法栈堆方法区运行时常量池直接内存 不同的虚拟机实现可能会略有差异。这些区域协同工作&#xff0c;支持Java…

(七)什么是Vite——vite优劣势、命令

vite分享ppt&#xff0c;感兴趣的可以下载&#xff1a; ​​​​​​​Vite分享、原理介绍ppt 什么是vite系列目录&#xff1a; &#xff08;一&#xff09;什么是Vite——vite介绍与使用-CSDN博客 &#xff08;二&#xff09;什么是Vite——Vite 和 Webpack 区别&#xff0…

计算机毕业设计 基于SpringBoot的健身房管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解目录

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

CrossEntropyLoss计算损失的时候可以传3维或者更高维的变量

CrossEntropyLoss计算损失的时候可以传3维或者更高维的变量&#xff1a; 从CrossEntropyLoss的源码的描述中可以看到答案&#xff1a; 红框中的minibatch表示batch_size&#xff0c; C表示class_num&#xff0c;后面可以跟着其他维度。 这样就可以使用3维或者更高维的变量&am…

基于nbiot的矿车追踪定位系统(论文+源码)

1.系统设计 鉴于智能物联网的大趋势&#xff0c;本次基于窄带物联网的矿车追踪定位系统应具备以下功能&#xff1a; &#xff08;1&#xff09;实现实时定位&#xff0c;真正实现矿车随时随地定位; &#xff08;2&#xff09;定位精度高&#xff0c;采用该系统可以实现矿车在…

Diagrams——制作短小精悍的流程图

今天为大家分享的是一款轻量级的流程图绘制软件——Diagrams。 以特定的图形符号加上说明&#xff0c;表示算法的图&#xff0c;称为流程图或框图。流程图是流经一个系统的信息流、观点流或部件流的图形代表。我们常用流程图来说明某一过程。 流程图使用一些标准符号代表某些类…

[C语言 数据结构] 栈

1.什么是栈&#xff1f; 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压…

合并两个有序链表(冒泡排序实现)

实例要求&#xff1a;将两个升序链表合并为一个新的 升序 链表并返回&#xff1b;新链表是通过拼接给定的两个链表的所有节点组成的&#xff1b;实例分析&#xff1a;先拼接两个链表&#xff0c;在使用冒泡排序即可&#xff1b;示例代码&#xff1a; struct ListNode* mergeTwo…

vs2017 编译Qt 5.11.2 源码

SDK 10.0.22000.194 有 2种编译方式 &#xff0c;第二种 看下面 方式一: 1、问题描述&#xff1a; 使用VS编译程序时&#xff0c;运行库选择多线程&#xff08;/MT&#xff09;&#xff0c;表示采用多线程静态release的方式进行编译。 但是&#xff0c;发现编译是不能通过的…

【C++】多态的使用详解

本篇要分享的内容是多态&#xff0c;以下为本篇目录。 目录 1.多态的概念 2. 多态的定义及实现 3.虚函数 4.C11 override和final 4.1final关键字 4.2override关键字 5.抽象类 5.1抽象类的概念 5.2接口继承和实现继承 1.多态的概念 通俗来说&#xff0c;就是多种形态…

RedisInsight——redis的桌面UI工具使用实践

下载 官网下载安装。下载地址在这里 填个邮箱地址就可以下载了。 安装使用。 安装成功后开始使用。 1. 你可以add一个地址。或者登录redis cloud 去auto-discover 2 . 新增你的redis库地址。注意index的取值 3。现在可以登录到redis了。看看结果 这是现在 在服务器上执行…

AC修炼计划(AtCoder Beginner Contest 328)

传送门&#xff1a; Toyota Programming Contest 2023#7&#xff08;AtCoder Beginner Contest 328&#xff09; - AtCoder 本章对于自己的提升&#xff1a;dfs的运用&#xff0c;带权并查集&#xff0c;以及状压dp。 A&#xff0c;B&#xff0c;C题比较简单&#xff0c;直接…

虚拟机配置完NAT模式之后可以和主机ping通但是ping 百度显示:网络不可达

具体linux网络配置看这&#xff1a;http://t.csdnimg.cn/KRami 解决方案如下&#xff1a; 如果这里网关为空&#xff0c;那么和我遇到的问题一样网关没有设置上&#xff0c;在这直接配置网关之后重启即可