天天看點

spring注解實作業務層事務管理,當業務層自調用時,事務失效問題解決

前些日子一朋友在需要在目标對象中進行自我調用,且需要實施相應的事務定義,且網上的一種通過BeanPostProcessor的解決方案是存在問題的。是以專門寫此篇文章分析why。

使用AOP 代理後的方法調用執行流程,如圖所示

spring注解實作業務層事務管理,當業務層自調用時,事務失效問題解決

也就是說我們首先調用的是AOP代理對象而不是目标對象,首先執行事務切面,事務切面内部通過TransactionInterceptor環繞增強進行事務的增強,即進入目标方法之前開啟事務,退出目标方法時送出/復原事務。

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() {  

目标對象内部的自我調用将無法實施切面中的增強,如圖所示

spring注解實作業務層事務管理,當業務層自調用時,事務失效問題解決

此處的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方法進行增強。

此處a方法中調用b方法時,隻要通過AOP代理調用b方法即可走事務切面,即可以進行事務增強,如下所示:

public void a() {  

aopProxy.b();//即調用AOP代理對象的b方法即可執行事務切面進行事務增強  

判斷一個Bean是否是AOP代理對象可以使用如下三種方法:

AopUtils.isAopProxy(bean)        : 是否是代理對象;

AopUtils.isCglibProxy(bean)       : 是否是CGLIB方式的代理對象;

AopUtils.isJdkDynamicProxy(bean) : 是否是JDK動态代理方式的代理對象;

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.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.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

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 Committing Hibernate transaction on Session …… ---送出b方法事務

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.interceptor.TransactionInterceptor Completing transaction for [com.sishuok.service.impl.AServiceImpl2.a] ----完成a方法事務

org.springframework.orm.hibernate4.HibernateTransactionManager Committing Hibernate transaction on Session ……---送出a方法事務

org.springframework.orm.hibernate4.HibernateTransactionManager Closing Hibernate Session …… after transaction  --關閉 a Session

此處我們可以看到b方法的事務起作用了。

以上方式是解決目标對象内部方法自我調用并實施事務的最簡單的解決方案。

4、實作原理分析

spring注解實作業務層事務管理,當業務層自調用時,事務失效問題解決

4.1、在進入代理對象之後通過AopContext.serCurrentProxy(proxy)暴露目前代理對象到ThreadLocal,并儲存上次ThreadLocal綁定的代理對象為oldProxy;

4.2、接下來我們可以通過 AopContext.currentProxy() 擷取目前代理對象;

4.3、在退出代理對象之前要重新将ThreadLocal綁定的代理對象設定為上一次的代理對象,即AopContext.serCurrentProxy(oldProxy)。

有些人不喜歡這種方式,說通過ThreadLocal暴露有性能問題,其實這個不需要考慮,因為事務相關的(Session和Connection)内部也是通過SessionHolder和ConnectionHolder暴露到ThreadLocal實作的。

不過自我調用這種場景确實隻有很少情況遇到,是以不用這種方式我們也可以通過如下方式實作。

@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);   

       proxySelf.b(); //④ 調用代理對象的方法 這樣可以執行事務切面  

此處日志就不分析,和3.1類似。此種方式不是很靈活,所有需要自我調用的實作類必須重複實作代碼。

BeanPostProcessor 的介紹和使用敬請等待我的下一篇分析帖。

一、定義BeanPostProcessor 需要使用的辨別接口

public interface BeanSelfAware {  

    void setSelf(Object proxyBean);  

 即我們自定義的BeanPostProcessor (InjectBeanSelfProcessor)如果發現我們的Bean是實作了該辨別接口就調用setSelf注入代理對象。

二、Bean實作

public class AServiceImpl4 implements AService, BeanSelfAware {//此處省略接口定義  

    private AService proxySelf;  

    public void setSelf(Object proxyBean) { //通過InjectBeanSelfProcessor注入自己(目标對象)的AOP代理對象  

        this.proxySelf = (AService) proxyBean;  

        proxySelf.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也可以使用此種方式  

        }  

postProcessAfterInitialization根據目标對象是否實作BeanSelfAware辨別接口,通過setSelf(bean)将代理對象(bean)注入到目标對象中,進而可以完成目标對象内部的自我調用。

關于BeanPostProcessor的執行流程等請一定參考我的這篇文章,否則無法繼續往下執行。

四、InjectBeanSelfProcessor的問題

(1、場景:通過InjectBeanSelfProcessor進行注入代理對象且循環依賴場景下會産生前者無法通過setSelf設定代理對象的問題。 循環依賴是應該避免的,但是實際工作中不可避免會有人使用這種注入,畢竟沒有強制性。

(2、用例

(2.1、定義BeanPostProcessor 需要使用的辨別接口

和3.1中一樣此處不再重複。

(2.2、Bean實作

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代理對象注入成功  

        self.b();  

public class BServiceImpl implements BService, BeanSelfAware {//此處省略Aservice接口定義  

    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代理對象注入成功  

此處A依賴B,B依賴A,即構成循環依賴,此處不探讨循環依賴的設計問題(實際工作應該避免循環依賴),隻探讨為什麼循環依賴會出現注入代理對象失敗的問題。

(2.3、InjectBeanSelfProcessor實作

和之前3.3中一樣 此處不再重複。

(2.4、測試用例

@RunWith(value = SpringJUnit4ClassRunner.class)  

@ContextConfiguration(value = {"classpath:spring-config.xml"})  

public class SelfInjectTest {  

    AService aService;  

    BService bService;  

    @Test  

    public void test() {  

執行如上測試用例會輸出:

BService=true

AService==false

即BService通過InjectBeanSelfProcessor注入代理對象成功,而AService卻失敗了(實際是注入了目标對象),如下是debug得到的資訊:

spring注解實作業務層事務管理,當業務層自調用時,事務失效問題解決
spring注解實作業務層事務管理,當業務層自調用時,事務失效問題解決

(2. 5、這是為什麼呢,怎麼在循環依賴會出現這種情況?

敬請期待我的下一篇分析帖。

public class InjectBeanSelfProcessor2 implements BeanPostProcessor, ApplicationContextAware {  

    //① 注入ApplicationContext  

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {  

        this.context = applicationContext;  

        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));  

縱觀其上:

【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

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的解決方案 】 可以作為最後的解決方案。

沒有完美的解決方案,隻有最适用的解決方案。