一 宏觀說明
[問題]
Spring的聲明式事務,我想就不用多介紹了吧,一句話“自從用了Spring AOP啊,事務管理真輕松啊,真輕松;事務管理代碼沒有了,腦不酸了,手不痛了,一口氣全配上了事務;輕量級,測試起來也簡單,嘿!”。不管從哪個角度看,輕量級聲明式事務都是一件解放生產力的大好事。所以,我們“一直用它”。
不過,最近的一個項目里,卻碰到了一個事務管理上的問題:有一個服務類,其一個聲明了事務的方法,里面做了三次插入SQL操作,但是在后面出錯回滾時,卻發現前面插入成功了,也是說,這個聲明了事務的方法,實際上並沒有真正啟動事務!怎么回事呢?難道Spring的聲明式事務失效了?
[探幽]
其實以前也會碰到有人說,Spring的事務配置不起作用,但是根據第一反應和以往經驗,我總會告訴他,肯定是你的配置有問題啦;所以這一次,我想也不會例外,大概是把事務注解配在了接口上而不是實現方法上,或者,如果是用XML聲明方式的話,很可能是切入點的表達式沒有配對。
不過,在檢查了他們的配置后,卻發現沒有配置問題,該起事務的實現方法上,用了@Transactional事務注解聲明,XML里也配了注解驅動<tx:annotation-driven .../>,配置很正確啊,怎么會不起作用?
我很納悶,於是往下問:
問1:其他方法有這種情況么?
答1:沒有。
問2:這個方法有什么特別的么(以下簡稱方法B)?
答2:就是調后台插了三條記錄啊,沒啥特別的。
問3:這個方法是從Web層直接調用的吧?
答3:不是,是這個Service類(以下簡稱ServiceA)的另外一個方法調過來的(以下簡稱方法A)。
問4:哦,那個調用它的方法配了事務么(問題可能在這了)?
答4:沒有。
問5:那WEB層的Action(用的是Struts2),調用的是沒有聲明事務的方法A,方法A再調用聲明了事務的方法B?
答5:對的。
問6:你直接在方法A上加上事務聲明看看
答6:好。。。
看來可能找到問題所在了,於是把@Transactional也加在方法A上,啟動項目測試,結果是:事務正常生效,方法A和方法B都在一個事務里了。
好了,現在總結一下現象:
1、ServiceA類為Web層的Action服務
2、Action調用了ServiceA的方法A,而方法A沒有聲明事務(原因是方法A本身比較耗時而又不需要事務)
3、ServiceA的方法A調用了自己的方法B,而方法B聲明了事務,但是方法B的事務聲明在這種情況失效了。
4、如果在方法A上也聲明事務,則在Action調用方法A時,事務生效,而方法B則自動參與了這個事務。
我讓他先把A也加上事務聲明,決定回來自己再測一下。
這個問題,表面上是事務聲明失效的問題,實質上很可能是Spring的AOP機制實現角度的問題。我想到很久以前研究Spring的AOP實現時發現的一個現象:對於以Cglib方式增強的AOP目標類,會創建兩個對象,一個事Bean實例本身,一個是Cglib增強代理對象,而不僅僅是只有后者。我曾經疑惑過這一點,但當時沒有再仔細探究下去。
我們知道,Spring的AOP實現方式有兩種:1、Java代理方式;2、Cglib動態增強方式,這兩種方式在Spring中是可以無縫自由切換的。Java代理方式的優點是不依賴第三方jar包,缺點是不能代理類,只能代理接口。
Spring通過AopProxy接口,抽象了這兩種實現,實現了一致的AOP方式:
現在看來,這種抽象同樣帶了一個缺陷,那就是抹殺了Cglib能夠直接創建普通類的增強子類的能力,Spring相當於把Cglib動態生成的子類,當普通的代理類了,這也是為什么會創建兩個對象的原因。下圖顯示了Spring的AOP代理類的實際調用過程:
因此,從上面的分析可以看出,methodB沒有被AopProxy通知到,導致最終結果是:被Spring的AOP增強的類,在同一個類的內部方法調用時,其被調用方法上的增強通知將不起作用。
而這種結果,會造成什么影響呢:
1:內部調用時,被調用方法的事務聲明將不起作用
2:換句話說,你在某個方法上聲明它需要事務的時候,如果這個類還有其他開發者,你將不能保證這個方法真的會在事務環境中
3:再換句話說,Spring的事務傳播策略在內部方法調用時將不起作用。不管你希望某個方法需要單獨事務,是RequiresNew,還是要嵌套事務,要Nested,等等,統統不起作用。
4:不僅僅是事務通知,所有你自己利用Spring實現的AOP通知,都會受到同樣限制。。。。
[解難]
問題的原因已經找到,其實,我理想中的AOP實現,應該是下面這樣:
只要一個Cglib增強對象就好,對於Java代理方式,我的選擇是毫不猶豫的拋棄。
至於前面的事務問題,只要避開Spring目前的AOP實現上的限制,要么都聲明要事務,要么分開成兩個類,要么直接在方法里使用編程式事務,那么一切OK。
前些日子一朋友在需要在目標對象中進行自我調用,且需要實施相應的事務定義,且網上的一種通過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、測試代碼准備
- 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方法即可走事務切面,即可以進行事務增強,如下所示:
判斷一個Bean是否是AOP代理對象可以使用如下三種方法:
AopUtils.isAopProxy(bean) : 是否是代理對象;
AopUtils.isCglibProxy(bean) : 是否是CGLIB方式的代理對象;
AopUtils.isJdkDynamicProxy(bean) : 是否是JDK動態代理方式的代理對象;
3.1、通過ThreadLocal暴露Aop代理對象
1、開啟暴露Aop代理到ThreadLocal支持(如下配置方式從spring3開始支持)
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、通過初始化方法在目標對象中注入代理對象
- @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 需要使用的標識接口
即我們自定義的BeanPostProcessor (InjectBeanSelfProcessor)如果發現我們的Bean是實現了該標識接口就調用setSelf注入代理對象。
二、Bean實現
- @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實現
- @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實現
- @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() {
- }
- }
- @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、測試用例
- @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的解決方案
- @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代理對象注入問題,即無法解決目標對象的自我調用問題。
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的解決方案 】 可以作為最后的解決方案。