类内部方法调用事务失效的原因
xsobi 2024-12-14 15:45 1 浏览
在spring工程中,一个类内部,a方法使用了@Transactional注解,b方法没有使用@Transactional注解,如果a方法体内调用了b方法,当先调用a方法时,b方法的事务不起作用 或者说 失效,这是为什么了?
首先,看看实验的例子。
依赖包:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!-- Spring JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
</dependencies>
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<!--<bean class="com.feng.ioc.model.User" id="userA">
<property name="id" value="1"></property>
<property name="name" value="feng"></property>
<!–<property name="myName" value="feng"></property>–>
</bean>-->
<!--自动扫包-->
<context:component-scan base-package="com.feng.aop"></context:component-scan>
<!--开启自动生成-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test_json?useSSL=false&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 启用注解驱动的事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
目标类和接口:
public interface UserService {
List<User> userList();
Boolean addUser();
}
@Service("UserService")
public class UserServiceImpl implements UserService{
@Autowired
private JdbcTemplate jdbcTempleate;
@Override
public List<User> userList() {
List<User> users = new ArrayList<User>();
User user1 = new User(1, "aa");
User user2 = new User(2, "bb");
users.add(user1);
users.add(user2);
addUser();
return users;
}
@Override
@Transactional
public Boolean addUser() {
jdbcTempleate.execute("insert into user ( name) values (\"ccc\")");
int i = 1/0;
jdbcTempleate.execute("insert into user ( name) values (\"ddd\")");
return true;
}
}
切面类:
@Component
@Aspect
public class TimeAspect {
@Around("execution(* com.feng.aop.service.UserServiceImpl.*(..))")
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
long begin = System.currentTimeMillis();
System.out.println("开始时间:" + begin);
Object proceed = joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("结束时间:" + end);
System.out.println("使用时间:" + (end - begin));
return proceed;
}
}
主类:
public class AopTest {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService)context.getBean("UserService");
// userService.addUser();
try {
userService.userList();
} catch (RuntimeException e) {
}
// 将jdk生成的代理类输出
byte[] bytes2 = ProxyGenerator.generateProxyClass("$Proxy21",new Class[]{UserService.class});
FileOutputStream os2 = new FileOutputStream("Proxy21.class");
os2.write(bytes2);
os2.close();
}
}
那么Spring工程启动后,动态创建代理对象的过程:
当spring执行到refresh()方法内的finishBeanFactoryInitialization(beanFactory)方法时,会实例化余下的(前面已经实例化了一些内置的类)所有非延时初始化的单例对象,在使用反射构造方法创建bean对象后,会调用initializeBean()方法初始化bean对象。
在initializeBean()方法内会调用各种初始化方法对bean对象的属性赋值,之后,会调用bean的后置处理器的postProcessAfterInitialization()方法对bean对象再处理,比如生成代理对象。(当然,在创建bean对象前,某些情况下的类也可以通过后置处理器创建代理对象,至于什么类,没做研究)
AnnotationAwareAspectJAutoProxyCreator这个bean后置处理器就是用来生成代理类并创建代理对象的。
在创建代理对象之前,会先查找advisors,哪些对象会成为advisors?
一是被@Aspect注解修饰的类,二是实现Advisor接口的类,除此之外,如果存在被@Aspect注解修饰的类,还会额外增加一个ExposeInvocationInterceptor.ADVISOR,这个类会放到advisors这个列表的第一位。也就是说,在本例中,我们有3个advisor。
查找好advisors后,就会创建ProxyFactory对象,再将advisors赋值给ProxyFactory对象的属性,由于我们的目标类使用了接口,Spring会调用JdkDynamicAopProxy类创建代理对象,在创建JdkDynamicAopProxy类的对象时,会将ProxyFactory对象传递给JdkDynamicAopProxy类的构造函数,所以JdkDynamicAopProxy对象也包含有advisors的信息,然后调用JdkDynamicAopProxy对象生成代理类字节码文件内容,再读入到JVM,创建代理类对象。
这是创建代理类对象的大概过程。
如果一个类有使用动态代理,那它的bean对象就不是由这个类创建的对象,而是由新生成的代理类创建的对象。调用的对象方法也是位于新生成的代理类,而不是原来的原始类。
生成代理类后,我们看看新生成代理类的class文件内容:
public final class $Proxy21 extends Proxy implements UserService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
private static Method m4;
public $Proxy21(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
....
}
public final List userList() throws {
try {
return (List)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
....
}
public final int hashCode() throws {
.....
}
public final Boolean addUser() throws {
try {
return (Boolean)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.feng.aop.service.UserService").getMethod("userList");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m4 = Class.forName("com.feng.aop.service.UserService").getMethod("addUser");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
为缩小篇幅,我将一些不重要的方法体省略了。
从输出新生成的代理类字节码可以看出,每个方法的方法体主要功能就是调用JdkDynamicAopProxy对象的invoke()方法(属性h保存的就是JdkDynamicAopProxy对象),目标类的方法 和 切面类的方法都是通过这个invoke()方法作为入口去执行。先执行advisors列表第一个元素的invoke(),执行到最后一个元素的invoke()时,会调用目标类的方法。
既然每个方法都使用相同的JdkDynamicAopProxy对象,那么JdkDynamicAopProxy对象里的advisors是不是对每个方法都起作用了?
不是的,在invoke()方法里会根据传递进来的方法反射信息进行筛选,由于目标类的userList方法没有使用@Transactional注解,所以先执行代理类的userList方法时就没有与事务相关的advisor,只有先执行代理类的addUser方法时才有事务相的advisor。
筛选好advisor后,就层层递归调用advisor的方法,最后才调用目标类对象的方法,如果目标类的方法之间有调用,也是目标类对象内部的方法调用,而不是新生成的代理类对象内部的方法调用,所以,在spring工程中,当一个没带@Transactional注解的方法调用一个带@Transactional注解的方法时,事务不起作用。
- 上一篇:多线程环境spring如何实现事务管理?
- 下一篇:20 分钟搞定注解
相关推荐
- 好用的云函数!后端低代码接口开发,零基础编写API接口
-
前言在开发项目过程中,经常需要用到API接口,实现对数据库的CURD等操作。不管你是专业的PHP开发工程师,还是客户端开发工程师,或者是不懂编程但懂得数据库SQL查询,又或者是完全不太懂技术的人,通过...
- 快速上手:Windows 平台上 cURL 命令的使用方法
-
在工作流程中,为了快速验证API接口有效性,团队成员经常转向直接执行cURL命令的方法。这种做法不仅节省时间,而且促进了团队效率的提升。对于使用Windows系统的用户来说,这里有一套详细...
- 使用 Golang net/http 包:基础入门与实战
-
简介Go的net/http包是构建HTTP服务的核心库,功能强大且易于使用。它提供了基本的HTTP客户端和服务端支持,可以快速构建RESTAPI、Web应用等服务。本文将介绍ne...
- #小白接口# 使用云函数,人人都能编写和发布自己的API接口
-
你只需编写简单的云函数,就可以实现自己的业务逻辑,发布后就可以生成自己的接口给客户端调用。果创云支持对云函数进行在线接口编程,进入开放平台我的接口-在线接口编程,设计一个新接口,设计和配置好接口参...
- 极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关
-
本内容来源于@什么值得买APP,观点仅代表作者本人|作者:iN在之前和大家说过,在iN的家里是没有墙面开关的。...
- window使用curl命令的注意事项 curl命令用法
-
cmd-使用curl命令的注意点前言最近在cmd中使用curl命令来测试restapi,发现有不少问题,这里记录一下。在cmd中使用curl命令的注意事项json不能由单引号包括起来json...
- Linux 系统curl命令使用详解 linuxctrl
-
curl是一个强大的命令行工具,用于在Linux系统中进行数据传输。它支持多种协议,包括HTTP、HTTPS、FTP等,用于下载或上传数据,执行Web请求等。curl命令的常见用法和解...
- Tornado 入门:初学者指南 tornados
-
Tornado是一个功能强大的PythonWeb框架和异步网络库。它最初是为了处理实时Web服务中的数千个同时连接而开发的。它独特的Web服务器和框架功能组合使其成为开发高性能Web...
- PHP Curl的简单使用 php curl formdata
-
本文写给刚入PHP坑不久的新手们,作为工具文档,方便用时查阅。CURL是一个非常强大的开源库,它支持很多种协议,例如,HTTP、HTTPS、FTP、TELENT等。日常开发中,我们经常会需要用到cur...
- Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介
-
本章涵盖使用Actix提供静态网页...
- 我给 Apache 顶级项目提了个 Bug apache顶级项目有哪些
-
这篇文章记录了给Apache顶级项目-分库分表中间件ShardingSphere提交Bug的历程。说实话,这是一次比较曲折的Bug跟踪之旅。10月28日,我们在GitHub上提...
- linux文件下载、服务器交互(curl)
-
基础环境curl命令描述...
- curl简单使用 curl sh
-
1.curl--help#查看关键字2.curl-A“(添加user-agent<name>SendUser-Agent<name>toserver)”...
- 常用linux命令:curl 常用linux命令大全
-
//获取网页内容//不加任何选项使用curl时,默认会发送GET请求来获取内容到标准输出$curlhttp://www.baidu.com//输出<!DOCTYPEh...
- 三十七,Web渗透提高班之hack the box在线靶场注册及入门知识
-
一.注册hacktheboxHackTheBox是一个在线平台,允许测试您的渗透技能和代码,并与其他类似兴趣的成员交流想法和方法。它包含一些不断更新的挑战,并且模拟真实场景,其风格更倾向于CT...
- 一周热门
- 最近发表
-
- 好用的云函数!后端低代码接口开发,零基础编写API接口
- 快速上手:Windows 平台上 cURL 命令的使用方法
- 使用 Golang net/http 包:基础入门与实战
- #小白接口# 使用云函数,人人都能编写和发布自己的API接口
- 极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关
- window使用curl命令的注意事项 curl命令用法
- Linux 系统curl命令使用详解 linuxctrl
- Tornado 入门:初学者指南 tornados
- PHP Curl的简单使用 php curl formdata
- Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- strcat (25)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- element style (30)
- dedecms模版 (53)
- vs打不开 (29)
- nmap (30)
- webgl开发 (24)
- parse (24)
- c 视频教程下载 (33)
- paddleocr (28)
- listview排序 (33)
- firebug 使用 (31)
- transactionmanager (30)
- characterencodingfilter (33)
- getmonth (34)
- commandtimeout (30)
- hibernate教程 (31)
- label换行 (33)
- curlpost (31)