Spring service本类中方法调用另一个方法事务不生效问题

转自CSDN 原文链接

前些日子一朋友在需要在目标对象中进行自我调用,且需要实施相应的事务定义,且网上的一种通过BeanPostProcessor的解决方案是存在问题的。因此专门写此篇帖子分析why。

1、预备知识

aop概念请参考【http://www.iteye.com/topic/1122401】和【http://jinnianshilongnian.iteye.com/blog/1418596

spring的事务管理,请参考【http://jinnianshilongnian.iteye.com/blog/1441271

使用AOP 代理后的方法调用执行流程,如图所示


a7d8d493-e387-34e9-a637-8a4d8d438602.jpg

也就是说我们首先调用的是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、问题

目标对象内部的自我调用将无法实施切面中的增强,如图所示

d33ee177-cf3f-39cc-befe-533306b0715f.jpg

此处的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开始支持)

  <aop:aspectj-autoproxy expose-proxy="true"/><!—注解风格支持-->
  <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、实现原理分析

11bcfc1c-06cd-3443-a3c6-944320476689.jpg
  • 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得到的信息:

abcbe77b-491a-3e3a-97fd-a560d01a886b.jpg
ff7eef80-207b-31ed-8fa8-08990fec1e82.jpg

(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代理对象注入问题,即无法解决目标对象的自我调用问题。

jingnianshilongnian 写道

spring允许的循环依赖(只考虑单例和原型Bean):
A----B
B----A
只有在A和B都不为原型是允许的,即如果A和B都是prototype则会报错(无法进行原型Bean的循环依赖)。
A(单例)---B(单例) 或 A(原型)---B(单例) 这是可以的,但 A(原型)---B(原型)或 A(原型)---B(单例Lazy)【且context.getBean("A")】时 这是不允许的。

一、A(原型)---B(原型) A(原型)---B(单例Lazy)【且context.getBean("A")】 会报:
Error creating bean with name 'BServiceImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.sishuok.issue.AService com.sishuok.issue.impl.BServiceImpl.aService; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'AServiceImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.sishuok.issue.BService com.sishuok.issue.impl.AServiceImpl.bService; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'BServiceImpl': Requested bean is currently in creation: [color=red]Is there an unresolvable circular reference[/color]?

二、A(原型)---B(单例) 和 A(单例)---B(单例)
这种方式 使用我的 【3.3 通过BeanPostProcessor 在目标对象中注入代理对象】 是没有问题的。

因此【 3.4 改进版的InjectBeanSelfProcessor的解决方案 】 可以作为最后的解决方案。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,565评论 18 399
  • 这部分主要是开源Java EE框架方面的内容,包括Hibernate、MyBatis、Spring、Spring ...
    杂货铺老板阅读 1,340评论 0 2
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,729评论 6 342
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,438评论 1 133