代理是指由被委托人接受委托人的委托全权去办理一件事情,在办理这件事情的过程中委托人会限制被委托人的行事边界。
哈喽 大家好,这次我想写一些关于我在工作中遇到的有关java动态代理问题的排查与解决,希望能够帮助自己总结的同时对网络上的其他小伙伴有些许的帮助。首先我要列举几个@Async和@Transactional的例子。
@Transactional
这种情况属于“自调用”的情况。自调用的意思就是通过CGLIB方式动态代理的方法,调用了类内其他方法。这种“自调用”的情况会使注解无效。具体代码如下:
@Service
public class Galaxy {
@Autowired
private UserRepository userRepository;
public void alpha(){
beta();//这就是“自调用”:在Galaxy类内注解的方法内调用了同类内的其他方法。
}
//对beta()方法开启了事务
@Transactional
public void beta(){
UserDomain UserDomain = new UserDomain();
UserDomain.setId(4L);
UserDomain.setName("周杰伦");
UserDomain.setAccount("Jay zhou");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
UserDomain = new UserDomain(); //断点
UserDomain.setId(3L);
UserDomain.setName("周华健");
UserDomain.setAccount("zhouhuajina");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
}
}
在beta()方法中打下断点,数据库中是没有事务的。说明注解没有生效。
mysql> select * from information_schema.innodb_trx \G;
Empty set (0.00 sec)
上面代码运行结果:
mysql> select * from user;
+----+-------------+-----------+--------+
| id | account | name | pwd |
+----+-------------+-----------+--------+
| 3 | zhouhuajina | 周华健 | 123456 |
| 4 | Jay zhou | 周杰伦 | 123456 |
+----+-------------+-----------+--------+
通过断点时的堆栈信息,我们发现实际调用beta()的是 Galaxy$$EnhancerBySpringCGLIB$$76b76f24
方法。
beta:44, Galaxy (com.example.service.galaxy)
alpha:31, Galaxy (com.example.service.galaxy)
invoke:-1, Galaxy$$EnhancerBySpringCGLIB$$76b76f24 (com.example.service.galaxy)
......
Galaxy\(EnhancerBySpringCGLIB\)76b76f24方法是通过cglib做动态代理时被jvm建立起来的一个虚拟的文件,这个文件是对Galxy类的增强类(GalxyEnhence),增强类继承了Galaxy类并对注解的类进行方法增强。方法增强指的是你使用的那个注解会把相关代码(注解开发人员早已经写好了的代码)用重写的方式将你的代码增强起来。所以,这个被jvm虚拟出来的文件大概应该长这样:
public class Galaxy$$EnhancerBySpringCGLIB$$76b76f24 extend Galaxy{
//因为beta方法被注解了,所以通过重写的方式增强这个方法。其他没有被注解的方法,不重写
@Override
public void beta(){
try {
// 开启事务
startTransaction();
UserDomain UserDomain = new UserDomain();
UserDomain.setId(4L);
UserDomain.setName("周杰伦");
UserDomain.setAccount("Jay zhou");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
UserDomain = new UserDomain();
UserDomain.setId(3L);
UserDomain.setName("周华健");
UserDomain.setAccount("zhouhuajina");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
} catch (Exception e) {
// 出现异常回滚事务
rollbackTransaction();
}
// 提交事务
commitTransaction();
}
}
但是,这段代码出现了一个问题:beta()的调用者是隐含的this
。这就是“自调用”的意思,那实际的调用链是:Galaxy.alpha() => Galaxy.beta()。它没有走我们的增强类的beta()!所以就出现了刚才的结果。按照这种思路可以这样改:
解决方案1:利用@Transactional的传递性,在总方法上加@Transactional。
@Service
public class Galaxy {
@Autowired
private UserRepository userRepository;
//解决方案1:将注解加到alpha上。
//借助@transactional的传递性,会把beta()也加到事务里来。
@Transactional
public void alpha(){
beta();
}
public void beta(){
UserDomain UserDomain = new UserDomain();
UserDomain.setId(4L);
UserDomain.setName("周杰伦");
UserDomain.setAccount("Jay zhou");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
UserDomain = new UserDomain(); //断点
UserDomain.setId(3L);
UserDomain.setName("周华健");
UserDomain.setAccount("zhouhuajina");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
}
}
在beta()方法中打下断点,数据库中出现了事务,说明起效了。
mysql> select * from information_schema.innodb_trx \G;
*************************** 1. row ***************************
trx_id: 422025326292896
trx_state: RUNNING
trx_started: 2021-12-12 21:54:14
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 0
trx_mysql_thread_id: 6022
trx_query: NULL
trx_operation_state: NULL
trx_tables_in_use: 0
trx_tables_locked: 0
trx_lock_structs: 0
trx_lock_memory_bytes: 1128
trx_rows_locked: 0
trx_rows_modified: 0
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLE READ
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 0
trx_is_read_only: 0
trx_autocommit_non_locking: 0
trx_schedule_weight: NULL
1 row in set (0.00 sec)
调用链为:Galaxy\(EnhancerBySpringCGLIB\)76b76f24.alpha()=> Galaxy\(EnhancerBySpringCGLIB\)76b76f24.beta()
解决方案2:在被代理类的外部调用其方法。
// Galaxy.java
@Service
public class Galaxy {
@Autowired
private UserService userService;
public void alpha(){
userService.beta();//调用者换成了外部类userService。
}
}
//UserService.java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
//将注解放到这里。
@Transactional
public void beta(){
UserDomain UserDomain = new UserDomain();
UserDomain.setId(4L);
UserDomain.setName("周杰伦");
UserDomain.setAccount("Jay zhou");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
UserDomain = new UserDomain(); //断点
UserDomain.setId(3L);
UserDomain.setName("周华健");
UserDomain.setAccount("zhouhuajina");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
}
}
调用链为:Galaxy.alpha => UserService.beta()。这其实不能算是一种解决方案,而是一种解决思路。
解决方案3:自注入
@Service
public class Galaxy {
//注解自己进入spring容器里。
@Autowired
private Galaxy galaxy
@Autowired
private UserRepository userRepository;
public void alpha(){
galaxy.beta(); //beta()的调用者换成了galaxy类,就可以让spring直接从容器里拿增强类。
}
@Transactional
public void beta(){
UserDomain UserDomain = new UserDomain();
UserDomain.setId(4L);
UserDomain.setName("周杰伦");
UserDomain.setAccount("Jay zhou");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
UserDomain = new UserDomain(); //断点
UserDomain.setId(3L);
UserDomain.setName("周华健");
UserDomain.setAccount("zhouhuajina");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
}
}
调用链为:Galaxy.alpha()=>Galaxy\(EnhancerBySpringCGLIB\)76b76f24.beta()
@Async 和 @Transactional
看下面的列子:
@Service
public class Galaxy {
@Autowired
private UserRepository userRepository;
@Transactional(rollbackFor = Exception.class)
public void alpha() throws Exception{
UserDomain UserDomain = new UserDomain();
UserDomain.setId(4L);
UserDomain.setName("周杰伦");
UserDomain.setAccount("Jay zhou");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
UserDomain = new UserDomain();
UserDomain.setId(3L);
UserDomain.setName("周华健");
UserDomain.setAccount("zhouhuajina");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
beta();
}
@Async(value = "beta")
public void beta() throws InterruptedException {
Thread.sleep(3000);
System.out.println("这是beta线程"); //断点
}
}
这个例子的意图是:alpha方法首先入库数据,然后调用异步方法beta。预期的效果应该是,alpha()直接插入数据,立刻退出。不会等到beta()中的数据输出。但是,实际情况却是alpha()在等待beta()3秒后,输出beta线程文案后退出。这意味着beta()上的@Async没有起效。
我在beta()中打了个断点,让我们看看当时的堆栈是什么情况:
beta:47, Galaxy (com.example.service.galaxy)
alpha:41, Galaxy (com.example.service.galaxy)
。。。。。
alpha:-1, Galaxy$$EnhancerBySpringCGLIB$$d543df13 (com.example.service.galaxy)
。。。。。
按照刚才的理论来讲,应该是jvm制造出来了一个增强类叫 Galaxy$$EnhancerBySpringCGLIB$$d543df13
,这个类继承了Galaxy类并且重写了alpha()和beta()。调用链应该是: Galaxy\(EnhancerBySpringCGLIB\)d543df13.alpha() => Galaxy.beta()。因为调用的是Galaxy.beta()而不是 Galaxy\(EnhancerBySpringCGLIB\)d543df13.beta(),所以@Async没有生效。
但是,按照上面解决方案2来重新构造代码发现@Async仍然没有生效,这就有些矛盾了。通过搜索找到了这篇issue。根据文中所述,是@Async和@Transactional的order先后的顺序导致了这个问题。因为spring开发者认为@Async的顺序应该是最优先的,所以即使@Async和@Transactional放在了一起,也会是先实现异步再进行事务。那接下来让我们试一试:
//Galaxy.java
@Service
public class Galaxy {
@Autowired
private UserRepository userRepository;
@Autowired
private UserService userService;
//1.先异步线程
@Async(value = "alpha")
public void alpha() throws Exception{
System.out.println("alpha thread start");
userService.beta(); //2.进入事务中
System.out.println("alpha thread end");
userService.gamma(); //3.抛出异常,回滚
}
}
//UserService.java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(rollbackFor = Exception.class)
public void beta() throws Exception{
System.out.println("这是beta线程");
UserDomain UserDomain = new UserDomain();
UserDomain.setId(4L);
UserDomain.setName("周杰伦");
UserDomain.setAccount("Jay zhou");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
UserDomain = new UserDomain();
UserDomain.setId(3L);
UserDomain.setName("周华健");
UserDomain.setAccount("zhouhuajina");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
}
public void gamma() throws Exception {
throw new Exception();
}
}
最终结果,符合预期,数据库里的数据被回滚了。然后再试一次两个注解在一起的情况:
//Galaxy.java
@Service
public class Galaxy {
@Autowired
private UserRepository userRepository;
@Autowired
private UserService userService;
//1.先异步线程
@Async(value = "alpha")
@Transactional(rollbackFor = Exception.class)
public void alpha() throws Exception{
System.out.println("alpha thread start");
userService.beta(); //2.进入事务中
System.out.println("alpha thread end");
userService.gamma(); //3.抛出异常,回滚
}
}
//UserService.java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void beta() throws Exception{
System.out.println("这是beta线程");
UserDomain UserDomain = new UserDomain();
UserDomain.setId(4L);
UserDomain.setName("周杰伦");
UserDomain.setAccount("Jay zhou");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
UserDomain = new UserDomain();
UserDomain.setId(3L);
UserDomain.setName("周华健");
UserDomain.setAccount("zhouhuajina");
UserDomain.setPwd("123456");
userRepository.save(UserDomain);
}
public void gamma() throws Exception {
throw new Exception();
}
}
最终,也符合预期。最后,至于注解的时序原理等我有时间研究一下再写出来。