现象描述

上周同事发现其基于mySql实现的分布式锁的线上代码存在问题,代码简化如下:

@Controller
class XService {@Autowiredprivate YService yService;public void doOutside(){this.doInside(); //或者直接doInside();效果是一样的}@Transactionalprivate void doInside(){//do sql statement}
}
@Controller
class Test {@Autowiredprivate XService xService;public void test(){xService.doOutside();}
}

实际执行test()后发现doInside()的Sql执行过程没有被Spring Transaction Manager管理起来。

发现的两个问题

  1. 在一个实例方法中调用被@Transactional注解标记的另一个方法,且两个方法都属于同一个类时,事务不会生效。
  2. 调用被@Transactional注解标记的非public方法,事务不会生效。

首先复习下相关知识:Spring AOP、JDK动态代理、CGLIB、AspectJ、@Aspect

@Transactional的实现原理是在业务方法外边通过Spring AOP包上一层事务管理器的代码(即插入切面),这是Java设计模式中常见的通过代理增强被代理类的做法。

Spring AOP的底层有2种实现:JDK动态代理、CGLIB。前者的原理是JDK反射,并且只支持Java接口的代理;后者的原理是继承(extend)与覆写(override),因此能支持普通的Java类的代理。两种方式都是动态代理,即运行时实时生成代理。

由于JVM的限制,CGLIB无法替换被代理类已经被载入的字节码,只能生成并载入一个新的子类作为代理类,被代理类的字节码依然存在于JVM中。

区别于前两者,AspectJ是一种静态代理的实现,即在编译时或者载入类时直接修改被代理类文件的字节码,而非运行时实时生成代理。因此这种方式需要额外的编译器或者JVM Agent支持,通过一些配置Spring和AspectJ也可以配合使用。

@Aspect一开始是AspectJ推出的Java注解形式,后来Spring AOP也支持使用这种形式表示切面,但实际上底层实现和AspectJ毫无关系,毕竟Spring AOP是动态代理,和静态代理是不兼容的。

进一步分析

既然事务管理器没有生效,那么首先需要确定一个问题:this到底是指向哪个对象,是未增强的XService还是增强后的XService?并且而且有没有可能已经调用增强后的实例和方法,但由于其他原因而导致事务管理器没有生效?

回忆下Java基础,this表示的是类的当前实例,那么关键就是确定类的实例是未被增强的XService(下面称其为XService),还是被CGLIB增强过的XService(下面称其为XService$$Cglib)。

在Test中,XService类的实例变量是一个由Spring框架管理的Bean,当执行test()时,根据@Autowired注解进行相应的注入,因此XService的实例实际为XService$$Cglib而不XService。被增强过的类的代码可以简化如下:

class XService$$Cglib extend XService {@Overridepublic doInside(){//开始事务的增强代码super.doInside();//结束事务的增强代码}
}

当执行XService$$Cglib.doOutside()时,由于子类没有覆写父类同名方法,因此实际上执行了父类XServicedoOutside()方法,所以在执行其this.doInside()时实际上调用的是父类未增强过的doInside(),因此事务管理器失效了。

这个问题在Spring AOP中广泛存在,即自调用,本质上是动态代理无法解决的盲区,只有AspectJ这类静态代理才能解决。

第二个问题则是Spring AOP不支持非public方法增强,与自调用类似,也是动态代理无法解决的盲区。

虽然CGLIB通过继承的方式是可以支持public、protected、package级别的方法增强的,但是由于JDK动态代理必须通过Java接口,只能支持public级别的方法,因此Spring AOP不得不取消非public方法的支持。

“自调用”的解决方法

1. 最好在被代理类的外部调用其方法

2. 自注入(Self Injection, from Spring 4.3)

@Controller
class XService {@Autowiredprivate YService yService;@Autowiredprivate XService xService;public void doOutside(){xService.doInside();//从this换成了xService}@Transactionalprivate void doInside(){//do sql statement}
}
@Controller
class Test {@Autowiredprivate XService xService;public void test(){xService.doOutside();}
}

由于xService变量是被Spring注入的,因此实际上指向XService$$Cglib对象,xService.doInside()因此也能正确的指向增强后的方法。

一种错误的解决办法:改造为Java接口的形式

@Controller
class XService implements IXService {@Autowiredprivate YService yService;@Overridepublic void doOutside(){this.doInside();}@Transactionalprivate void doInside(){//do sql statement}
}
@Controller
class Test {@Autowiredprivate IXService iXService;public test(){iXService.doOutside();}
}

原因是之前错误地理解事务未生效的原理:如果没有在xml中要设置只用CGLIB,@Transactional只能使用JDK动态代理,所以如果没有用Java接口方式进行代理就不会生效。

实际上,这还是避免不了自调用的问题,因为这是动态代理的普遍问题,无论是JDK动态代理还是CGLIB动态代理。

总结

使用Spring AOP的时候一定要小心,如果是使用注解形式声明AOP,要保证在被代理类的外部调用被增强的方法。

Reference

  1. Spring AOP 实现原理与 CGLIB 应用
  2. 关于spring的aop拦截的问题 protected方法代理问题
  3. 透彻的掌握 Spring 中@transactional 的使用
  4. Spring @Transactional原理及使用

【Spring】一次线上@Transational事务注解未生效的原因探究相关推荐

  1. mysql爆内存_线上MySQL数据库机器内存爆掉原因分析与解决

    本文主要向大家介绍了线上MySQL数据库机器内存爆掉原因分析与解决,通过具体的内容向大家展现,希望对大家学习MySQL数据库有所帮助. 现象: 阿里金融某业务的MySQL机器的内存每隔几天就会增长,涨 ...

  2. 记几次 [线上环境] Dubbo 线程池占满原因分析(第三次:GC STW)

    [线上环境] Dubbo 线程池占满原因排查系列 记几次 [线上环境] Dubbo 线程池占满原因分析(第一次:HttpClient) 记几次 [线上环境] Dubbo 线程池占满原因分析(第二次:C ...

  3. @Data注解不生效的原因

    今天遇到@Data注解不生效的问题 @Data注解可以节省get,set等方法 解决方法 在idea中安装插件 安装完成之后重启idea 问题解决

  4. Spring Cloud落地之Spring Cloud LoadBalancer 线上优化方案

    文章目录 Spring Cloud 版本 注册中心 Spring Cloud LoadBalancer 负载均衡器新的选择 缓存问题 解决方式 总结 这里是weihubeats,觉得文章不错可以关注公 ...

  5. 记录spring cloud项目线上JVM配置

    linux硬盘详情: # df -h Filesystem Size Used Avail Use% Mounted on /dev/vda1 99G 35G 60G 37% / tmpfs 7.8G ...

  6. 【小5聊】小程序之体验版本wx.chooseImage选择图片上传可以,而线上正式版本选择图片一闪而过的原因

    [问题描述] 在提交版本审核前,已经在体验本测试过,通过wx.chooseImage这个微信api接口是可以上传图片的.审核通过后,通过使用线上正式版本,发现选择图片后,一闪而过,然后就没反应了. [ ...

  7. @Controler注解未生效报No mapping found for HTTP request with URI 错误

    查看一下Web.xml文件中是否配置了<mvc: annotation-driven />,如果没有配置,添加上即可 之所以报No mapping found for HTTP reque ...

  8. Spring 事务注解@Transactional使用注意事项

    缺省情况下,Spring 事务基于Spring AOP技术,此时使用事务注解 @Transactional 需要留意以下问题 : 1. 不要在 protected,private 或者包内可见方法上使 ...

  9. 解决Spring事务注解@Transactional在类内部方法调用不生效的问题

    问题现象: package cn.sw.study.web.service.impl; import cn.sw.study.web.dao.UserMapper; import cn.sw.stud ...

最新文章

  1. python数字组合算法_面试宝典_Python.常规算法.0001.在圆圈和框框分别填入1~8数字各一次?...
  2. CSS 颜色 表示方法
  3. iOS ARC环境下dealloc的使用
  4. yoyo跑_面对“跑腿服务坑”:悠悠跑腿、蜂鸟配送、快跑者,跑腿公司该何去何从?...
  5. 计算机培训开场语,辅导班家长会主持词开场白
  6. XWiki 6.3 M1 发布,Java 的 Wiki 系统
  7. SpringMVC中异常捕获
  8. Mosets Tree开发笔记
  9. 关于郑州大学校园网锐捷客户端禁止热点分享,禁止多网卡的解决办法
  10. 集成学习01_xgboost参数讲解与实战
  11. 表格排版及其表格嵌套
  12. 【mmdetection】测试单张图像
  13. 云服务器 架设传奇_传奇私服架设教程
  14. window下isa防火墙详细安装
  15. android mcc 模拟,Android 读取MCC, MNC
  16. php+后台+限制+登录次数,THINKSNS取消后台登录密码错误次数限制的办法详解
  17. 微信公众平台测试号接口配置的一个坑——ngrok的
  18. Android中实现红绿灯动画,android红绿灯制作
  19. 数据库规范化三个范式应用实例
  20. java ocx,Eclipse使用ActiveX控件(OCX)开发Java程序

热门文章

  1. Copy Clone
  2. 阿里巴巴合伙人闻佳:创新背后的文化与组织
  3. 2017-2018-2 20155203《网络对抗技术》 Exp7:网络欺诈防范
  4. 386bsd 0.0
  5. 如何把Eclipse工程导入到Android Studio
  6. 测试AtomicInteger与普通int值在多线程下的递增操作
  7. iptables 学习笔记 二 参数指令
  8. Leetcode -MySQL-178. 分数排名
  9. Redis分布式锁问题
  10. Elasticsearch(六) java代码操作Es进行高级查询match、match_all