Spring事务处理时自我调用的解决方案及一些实现方式的风险
前些日子一朋友在需要在目标对象中进行自我调用,且需要实施相应的事务定义,且网上的一种通过BeanPostProcessor的解决方案是存在问题的。因此专门写此篇帖子分析why。
1、预备知识
aop概念请参考【http://www.iteye.com/topic/1122401】和【http://jinnianshilongnian.iteye.com/blog/1418596】
spring的事务管理,请参考【http://jinnianshilongnian.iteye.com/blog/1441271】
使用AOP 代理后的方法调用执行流程,如图所示
也就是说我们首先调用的是AOP代理对象而不是目标对象,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务。
2、测试代码准备
Java代码
- public interface AService {
- public void a();
- public void b();
- }
- @Service()
- public class AServiceImpl1 implements AService{
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- this.b();
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
3、问题
目标对象内部的自我调用将无法实施切面中的增强,如图所示
此处的this指向目标对象,因此调用this.b()将不会执行b事务切面,即不会执行事务增强,因此b方法的事务定义“@Transactional(propagation = Propagation.REQUIRES_NEW)”将不会实施,即结果是b和a方法的事务定义是一样的,可以从以下日志看出:
org.springframework.transaction.annotation.AnnotationTransactionAttributeSource Adding transactional method 'a' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
org.springframework.beans.factory.support.DefaultListableBeanFactory Returning cached instance of singleton bean 'txManager'
org.springframework.orm.hibernate4.HibernateTransactionManager Creating new transaction with name [com.sishuok.service.impl.AServiceImpl1.a]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' -----创建a方法事务
org.springframework.orm.hibernate4.HibernateTransactionManager Opened new Session …… for Hibernate transaction ---打开Session
……
org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization
org.springframework.transaction.interceptor.TransactionInterceptor Getting transaction for [com.sishuok.service.impl.AServiceImpl1.a]
org.springframework.transaction.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl1.a] ----完成a方法事务
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCompletion synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Initiating transaction commit
org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session ……---提交a方法事务
或
org.springframework.orm.hibernate4.HibernateTransactionManager Rolling back Hibernate transaction on Session ……---如果有异常将回滚a方法事务
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCompletion synchronization
org.springframework.transaction.support.TransactionSynchronizationManager Clearing transaction synchronization
……
org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction --关闭Session
我们可以看到事务切面只对a方法进行了事务增强,没有对b方法进行增强。
3、解决方案
此处a方法中调用b方法时,只要通过AOP代理调用b方法即可走事务切面,即可以进行事务增强,如下所示:
Java代码
- public void a() {
- aopProxy.b();//即调用AOP代理对象的b方法即可执行事务切面进行事务增强
- }
判断一个Bean是否是AOP代理对象可以使用如下三种方法:
AopUtils.isAopProxy(bean) : 是否是代理对象;
AopUtils.isCglibProxy(bean) : 是否是CGLIB方式的代理对象;
AopUtils.isJdkDynamicProxy(bean) : 是否是JDK动态代理方式的代理对象;
3.1、通过ThreadLocal暴露Aop代理对象
1、开启暴露Aop代理到ThreadLocal支持(如下配置方式从spring3开始支持)
Java代码
- <aop:aspectj-autoproxy expose-proxy="true"/><!—注解风格支持-->
Java代码
- <aop:config expose-proxy="true"><!—xml风格支持-->
2、修改我们的业务实现类
this.b();-----------修改为--------->((AService) AopContext.currentProxy()).b();
3、执行测试用例,日志如下
org.springframework.beans.factory.support.DefaultListableBeanFactory Returning cached instance of singleton bean 'txManager'
org.springframework.orm.hibernate4.HibernateTransactionManager Creating new transaction with name [com.sishuok.service.impl.AServiceImpl2.a]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '' -----创建a方法事务
org.springframework.orm.hibernate4.HibernateTransactionManager Opened new Session ……for Hibernate transaction --打开a Session
org.springframework.orm.hibernate4.HibernateTransactionManager Preparing JDBC Connection of Hibernate Session ……
org.springframework.orm.hibernate4.HibernateTransactionManager Exposing Hibernate transaction as JDBC transaction ……
……
org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization
org.springframework.transaction.interceptor.TransactionInterceptor Getting transaction for [com.sishuok.service.impl.AServiceImpl2.a]
org.springframework.transaction.annotation.AnnotationTransactionAttributeSource Adding transactional method 'b' with attribute: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT; ''
……
org.springframework.orm.hibernate4.HibernateTransactionManager Suspending current transaction, creating new transaction with name [com.sishuok.service.impl.AServiceImpl2.b] -----创建b方法事务(并暂停a方法事务)
……
org.springframework.orm.hibernate4.HibernateTransactionManager Opened new Session for Hibernate transaction ---打开b Session
……
org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization
org.springframework.transaction.interceptor.TransactionInterceptor Getting transaction for [com.sishuok.service.impl.AServiceImpl2.b]
org.springframework.transaction.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl2.b] ----完成b方法事务
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCompletion synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Initiating transaction commit
org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session …… ---提交b方法事务
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCompletion synchronization
org.springframework.transaction.support.TransactionSynchronizationManager Clearing transaction synchronization
……
org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction --关闭 b Session
-----到此b方法事务完毕
org.springframework.orm.hibernate4.HibernateTransactionManager Resuming suspended transaction after completion of inner transaction ---恢复a方法事务
……
org.springframework.transaction.support.TransactionSynchronizationManager Initializing transaction synchronization
org.springframework.transaction.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl2.a] ----完成a方法事务
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering beforeCompletion synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Initiating transaction commit
org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session ……---提交a方法事务
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCommit synchronization
org.springframework.orm.hibernate4.HibernateTransactionManager Triggering afterCompletion synchronization
org.springframework.transaction.support.TransactionSynchronizationManager Clearing transaction synchronization
……
org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction --关闭 a Session
此处我们可以看到b方法的事务起作用了。
以上方式是解决目标对象内部方法自我调用并实施事务的最简单的解决方案。
4、实现原理分析
4.1、在进入代理对象之后通过AopContext.serCurrentProxy(proxy)暴露当前代理对象到ThreadLocal,并保存上次ThreadLocal绑定的代理对象为oldProxy;
4.2、接下来我们可以通过 AopContext.currentProxy() 获取当前代理对象;
4.3、在退出代理对象之前要重新将ThreadLocal绑定的代理对象设置为上一次的代理对象,即AopContext.serCurrentProxy(oldProxy)。
有些人不喜欢这种方式,说通过ThreadLocal暴露有性能问题,其实这个不需要考虑,因为事务相关的(Session和Connection)内部也是通过SessionHolder和ConnectionHolder暴露到ThreadLocal实现的。
不过自我调用这种场景确实只有很少情况遇到,因此不用这种方式我们也可以通过如下方式实现。
3.2、通过初始化方法在目标对象中注入代理对象
Java代码
- @Service
- public class AServiceImpl3 implements AService{
- @Autowired //① 注入上下文
- private ApplicationContext context;
- private AService proxySelf; //② 表示代理对象,不是目标对象
- @PostConstruct //③ 初始化方法
- private void setSelf() {
- //从上下文获取代理对象(如果通过proxtSelf=this是不对的,this是目标对象)
- //此种方法不适合于prototype Bean,因为每次getBean返回一个新的Bean
- proxySelf = context.getBean(AService.class);
- }
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- proxySelf.b(); //④ 调用代理对象的方法 这样可以执行事务切面
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
此处日志就不分析,和3.1类似。此种方式不是很灵活,所有需要自我调用的实现类必须重复实现代码。
3.3、通过BeanPostProcessor 在目标对象中注入代理对象
此种解决方案可以参考http://fyting.iteye.com/blog/109236。
BeanPostProcessor 的介绍和使用敬请等待我的下一篇分析帖。
一、定义BeanPostProcessor 需要使用的标识接口
Java代码
- public interface BeanSelfAware {
- void setSelf(Object proxyBean);
- }
即我们自定义的BeanPostProcessor (InjectBeanSelfProcessor)如果发现我们的Bean是实现了该标识接口就调用setSelf注入代理对象。
二、Bean实现
Java代码
- @Service
- public class AServiceImpl4 implements AService, BeanSelfAware {//此处省略接口定义
- private AService proxySelf;
- public void setSelf(Object proxyBean) { //通过InjectBeanSelfProcessor注入自己(目标对象)的AOP代理对象
- this.proxySelf = (AService) proxyBean;
- }
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- proxySelf.b();//调用代理对象的方法 这样可以执行事务切面
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
实现BeanSelfAware标识接口的setSelf将代理对象注入,并且通过“proxySelf.b()”这样可以实施b方法的事务定义。
三、InjectBeanSelfProcessor实现
Java代码
- @Component
- public class InjectBeanSelfProcessor implements BeanPostProcessor {
- public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- return bean;
- }
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- if(bean instanceof BeanSelfAware) {//如果Bean实现了BeanSelfAware标识接口,就将代理对象注入
- ((BeanSelfAware) bean).setSelf(bean); //即使是prototype Bean也可以使用此种方式
- }
- return bean;
- }
- }
postProcessAfterInitialization根据目标对象是否实现BeanSelfAware标识接口,通过setSelf(bean)将代理对象(bean)注入到目标对象中,从而可以完成目标对象内部的自我调用。
关于BeanPostProcessor的执行流程等请一定参考我的这篇帖子,否则无法继续往下执行。
四、InjectBeanSelfProcessor的问题
(1、场景:通过InjectBeanSelfProcessor进行注入代理对象且循环依赖场景下会产生前者无法通过setSelf设置代理对象的问题。 循环依赖是应该避免的,但是实际工作中不可避免会有人使用这种注入,毕竟没有强制性。
(2、用例
(2.1、定义BeanPostProcessor 需要使用的标识接口
和3.1中一样此处不再重复。
(2.2、Bean实现
Java代码
- @Service
- public class AServiceImpl implements AService, BeanSelfAware {//此处省略Aservice接口定义
- @Autowired
- private BService bService; //① 通过@Autowired方式注入BService
- private AService self; //② 注入自己的AOP代理对象
- public void setSelf(Object proxyBean) {
- this.self = (AService) proxyBean; //③ 通过InjectBeanSelfProcessor注入自己(目标对象)的AOP代理对象
- System.out.println("AService=="+ AopUtils.isAopProxy(this.self)); //如果输出true标识AOP代理对象注入成功
- }
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- self.b();
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
Java代码
- @Service
- public class BServiceImpl implements BService, BeanSelfAware {//此处省略Aservice接口定义
- @Autowired
- private AService aService; //① 通过@Autowired方式注入AService
- private BService self; //② 注入自己的AOP代理对象
- public void setSelf(Object proxyBean) { //③ 通过InjectBeanSelfProcessor注入自己(目标对象)的AOP代理对象
- this.self = (BService) proxyBean;
- System.out.println("BService=" + AopUtils.isAopProxy(this.self)); //如果输出true标识AOP代理对象注入成功
- }
- @Transactional(propagation = Propagation.REQUIRED)
- public void a() {
- self.b();
- }
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void b() {
- }
- }
此处A依赖B,B依赖A,即构成循环依赖,此处不探讨循环依赖的设计问题(实际工作应该避免循环依赖),只探讨为什么循环依赖会出现注入代理对象失败的问题。
循环依赖请参考我的博文【http://jinnianshilongnian.iteye.com/blog/1415278】。
依赖的初始化和销毁顺序请参考我的博文【http://jinnianshilongnian.iteye.com/blog/1415461】。
(2.3、InjectBeanSelfProcessor实现
和之前3.3中一样 此处不再重复。
(2.4、测试用例
Java代码
- @RunWith(value = SpringJUnit4ClassRunner.class)
- @ContextConfiguration(value = {"classpath:spring-config.xml"})
- public class SelfInjectTest {
- @Autowired
- AService aService;
- @Autowired
- BService bService;
- @Test
- public void test() {
- }
- }
执行如上测试用例会输出:
BService=true
AService==false
即BService通过InjectBeanSelfProcessor注入代理对象成功,而AService却失败了(实际是注入了目标对象),如下是debug得到的信息:
(2. 5、这是为什么呢,怎么在循环依赖会出现这种情况?
敬请期待我的下一篇分析帖。
3.4、改进版的InjectBeanSelfProcessor的解决方案
Java代码
- @Component
- public class InjectBeanSelfProcessor2 implements BeanPostProcessor, ApplicationContextAware {
- private ApplicationContext context;
- //① 注入ApplicationContext
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.context = applicationContext;
- }
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- if(!(bean instanceof BeanSelfAware)) { //② 如果Bean没有实现BeanSelfAware标识接口 跳过
- return bean;
- }
- if(AopUtils.isAopProxy(bean)) { //③ 如果当前对象是AOP代理对象,直接注入
- ((BeanSelfAware) bean).setSelf(bean);
- } else {
- //④ 如果当前对象不是AOP代理,则通过context.getBean(beanName)获取代理对象并注入
- //此种方式不适合解决prototype Bean的代理对象注入
- ((BeanSelfAware)bean).setSelf(context.getBean(beanName));
- }
- return bean;
- }
- public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- return bean;
- }
- }
5、总结
纵观其上:
【3.1 通过ThreadLocal暴露Aop代理对象】适合解决所有场景(不管是singleton Bean还是prototype Bean)的AOP代理获取问题(即能解决目标对象的自我调用问题);
【3.2 通过初始化方法在目标对象中注入代理对象】 和【3.4 改进版的InjectBeanSelfProcessor的解决方案】能解决普通(无循环依赖)的AOP代理对象注入问题,而且也能解决【3.3】中提到的循环依赖(应该是singleton之间的循环依赖)造成的目标对象无法注入AOP代理对象问题,但该解决方案不适合解决循环依赖中包含prototype Bean的自我调用问题;
【3.3 通过BeanPostProcessor 在目标对象中注入代理对象】:只能解决 普通(无循环依赖)的 的Bean注入AOP代理,无法解决循环依赖的AOP代理对象注入问题,即无法解决目标对象的自我调用问题。
没有完美的解决方案,只有最适用的解决方案。
测试代码请参考附件,jar包与http://www.iteye.com/topic/1120924使用的是一样的
from:http://jinnianshilongnian.iteye.com/blog/1487235
Spring事务处理时自我调用的解决方案及一些实现方式的风险相关推荐
- Spring AOP中自我调用的问题
前几天在做项目的时候同事说,在使用AOP进行拦截的时候发现有些方法有时候能输出拦截的日志有时候不输出拦截的日志.发现在单独调用这些方法的时候是有日志输出,在被同一个类中的方法调用的时候没有日志输出.我 ...
- 【spring源码学习】spring的aop目标对象中进行自我调用,且需要实施相应的事务定义的解决方案...
转载:http://www.iteye.com/topic/1122740 1.预备知识 aop概念请参考[http://www.iteye.com/topic/1122401]和[http://ji ...
- Spring Boot——不同环境调用不同的配置文件解决方案
问题描述 我们在开发Spring Boot应用时,通常同一套程序会被应用和安装到几个不同的环境,比如:开发.测试.生产等.其中每个环境的数据库地址.服务器端口等等配置都会不同,如果在为不同环境打包时都 ...
- Shiro与Spring集成时,Shiro权限注解@RequiresRoles等不生效的解决方案
2019独角兽企业重金招聘Python工程师标准>>> Shiro与Spring集成时,Shiro权限注解@RequiresRoles等不生效, 这个问题着实整了好久,网上各种解决方 ...
- Spring事务处理之 编程式事务 和 声明式事务
对事务概念还不了解的可以先看一下 MySQL事务管理(初步了解) 这篇文章 !!! Spring事务管理学习步骤: 1.掌握Spring事务属性 2.掌握Spring事务核心API 3.掌握Sprin ...
- Java包数据消息头消息尾_读Socket流时产生阻塞的解决方案(粘包拆包问题)
转自:https://www.cnblogs.com/qhyuan1992/p/5385289.html 其实最终讨论的是TCP通信过程中的粘包拆包(半包)问题. 在用socket写一个服务器时遇到了 ...
- Spring Validation的校验顺序问题解决方案(建议读到最后,有大坑)
Spring Validation的校验顺序问题解决方案 问题场景 原理剖析 解决方法 创建五个接口 修改Controller控制层代码 修改实体类代码 整改结果 后续问题 问题原因 解决方案 问题场 ...
- Spring构造器注入循环依赖的解决方案及原理探索
前言 我们都知道Spring解决了Setter注入或者Field注入的循环依赖问题,依靠的是三个Map(earlySingletonObjects.singletonFactories.singlet ...
- 《Spring技术内幕》学习笔记19——Spring RMI实现远程调用
1.Spring除了使用基于HTTP协议的远程调用方案,还为开发者提供了基于RMI机制的远程调用方法,RMI远程调用网络通信实现是基于TCP/IP协议完成的,而不是通过HTTP协议. 在Spring ...
最新文章
- 程序员搞事!动手实战优化自己公司线上系统JVM,结果。。。
- 介绍几种SSIS部署方式
- js在上传图片前判断大小
- 几种在Linux下查询外网IP的办法
- 记一次某APK的恶意WIFI攻击
- 【Elasticsearch】 Elasticsearch对外提供分词服务实践
- inspect python模块_Python inspect模块:仅限关键字参数
- “光伏热”背后存隐忧 竞价上网倒逼产业升级
- 三星emcp型号详解_三星emcp型号详解_eMCP终将成为过去式?解析:三星、美光所推出的uMCP为何物?......
- 人才缺口上百万,年薪50万+!
- unity获取麦克风音量_Unity获取麦克风音量(实现音效波浪效果)
- 第9个HttpClient 例子,HttpClient+jsoup 扩展获取网站信息
- 语义计算_语义多态性如何在量子计算中起作用
- 人工智能就业越来越严峻,你还在坚持吗
- 硬件探索——STM32F4控制74HC595数码管
- 哪个牌子蓝牙耳机打电话清晰?通话最清晰的蓝牙耳机推荐
- ElsaticSearch为什么搜索很快
- Ajax异步请求的步骤
- 用python的openpyxl库实现对excel工作表的自动化操作
- 22.Isaac教程--使用 Isaac Sight可视化
热门文章
- select语句for update---转载
- 机器学习-数据科学库(第二天)
- 深入理解分布式技术 - 降级和熔断
- 白话Elasticsearch18-深度探秘搜索技术之基于slop参数实现近似匹配以及原理剖析
- Java学习笔记(十一)--类与对象
- c语言 1 2是什么类型,C语言2-C数据类型(1).docx
- oracle日期相减工作日_Oracle 计算两个日期间隔的天数、月数和年数
- Java中的nextInt()和next()与nextLine()区别详解
- 加密生成指定长度_那些奇奇怪怪的需求(一):PinyinHelper的使用、生成指定长度的随机码...
- Python知识:opencv实现的直方图