Spring原始碼學習之十二:@Transactional是如何工作的

wind瑞_發表於2017-10-19

結合Spring框架,在進行資料庫操作的時候,經常會使用@Transactional註解,工作的經歷中看到很多人使用方式都是錯誤的,沒有深入理解過其原理,其實這是很危險的!!本篇將深入原始碼分析@Transactional註解的工作原理。

首先從tx:annotation-driven說起。配置了tx:annotation-driven,就必定有對應的標籤解析器類,檢視NamespaceHandler介面的實現類,可以看到一個TxNamespaceHandler,它註冊了AnnotationDrivenBeanDefinitionParser對annotation-driven元素進行解析。

進入AnnotationDrivenBeanDefinitionParser類,重點看parse方法。

從程式碼中可以看出,如果tx:annotation-driven中沒有配置mode引數,則預設使用代理模式進行後續處理;如果配置了mode=aspectj,則使用aspectj程式碼織入模式進行後續處理。

本篇分析使用代理模式的程式碼,進入AopAutoProxyConfigurer.configureAutoProxyCreator方法。

上圖程式碼中標出了一行核心程式碼,容易被忽略。進入AopNamespaceUtils.registerAutoProxyCreatorIfNecessary方法。

重點關注上圖中標出的程式碼,進入AopConfigUtils.registerAutoProxyCreatorIfNecessary方法。

上圖中的程式碼向Spring容器中註冊了一個InfrastructureAdvisorAutoProxyCreator類。可能會疑問為什麼要註冊這個類,有什麼作用?檢視InfrastructureAdvisorAutoProxyCreator類繼承關係。

通過上圖中的關係,可以發現InfrastructureAdvisorAutoProxyCreator間接實現了BeanPostProcessor介面,從AbstractAutoProxyCreator類中繼承了postProcessAfterInitialization方法。Spring容器在初始化每個單例bean的時候,會遍歷容器中的所有BeanPostProcessor實現類,並執行其postProcessAfterInitialization方法。

進入AbstractAutoProxyCreator類的postProcessAfterInitialization方法。

其中wrapIfNecessary方法是建立代理物件的核心方法。

getAdvicesAndAdvisorsForBean方法會遍歷容器中所有的切面,查詢與當前例項化bean匹配的切面,這裡就是獲取事務屬性切面,查詢@Transactional註解及其屬性值,具體實現比較複雜,這裡暫不深入分析,最終會得到BeanFactoryTransactionAttributeSourceAdvisor例項,然後根據得到的切面進入createProxy方法,建立一個AOP代理。

進入ProxyFactory.getProxy方法。

createAopProxy方法決定使用JDK還是Cglib建立代理。

可以看出預設是使用JDK動態代理建立代理,如果目標類是介面,則使用JDK動態代理,否則使用Cglib。這裡分析使用JDK動態代理的方式,進入JdkDynamicAopProxy.getProxy方法。

可以看到很熟悉的建立代理的程式碼Proxy.newProxyInstance。這裡要注意的是,newProxyInstance方法的最後一個引數是JdkDynamicAopProxy類本身,也就是說在對目標類進行呼叫的時候,會進入JdkDynamicAopProxy的invoke方法。

這裡只關注JdkDynamicAopProxy的invoke方法的重點程式碼。

this.advised.getInterceptorsAndDynamicInterceptionAdvice獲取的是當前目標方法對應的攔截器,裡面是根據之前獲取到的切面來獲取相對應攔截器,這時候會得到TransactionInterceptor例項。如果獲取不到攔截器,則不會建立MethodInvocation,直接呼叫目標方法。這裡使用TransactionInterceptor建立一個ReflectiveMethodInvocation例項,呼叫的時候進入ReflectiveMethodInvocation的proceed方法。

程式碼中的interceptorOrInterceptionAdvice就是TransactionInterceptor的例項,執行invoke方法進入TransactionInterceptor的invoke方法。

TransactionInterceptor從父類TransactionAspectSupport中繼承了invokeWithinTransaction方法。

可以看到,在需要進行事務操作的時候,Spring會在呼叫目標類的目標方法之前進行開啟事務、呼叫異常回滾事務、呼叫完成會提交事務。

是否需要開啟新事務,是根據@Transactional註解上配置的引數值來判斷的。如果需要開啟新事務,獲取Connection連線,然後將連線的自動提交事務改為false,改為手動提交。

當對目標類的目標方法進行呼叫的時候,若發生異常將會進入completeTransactionAfterThrowing方法。

Spring並不會對所有型別異常都進行事務回滾操作,預設是隻對Unchecked Exception(Error和RuntimeException)進行事務回滾操作。

總結
從上面的分析可以看到,Spring使用AOP實現事務的統一管理,為開發者提供了很大的便利。但是,有部分開發人員會誤用這個便利,基本都是下面這兩種情況:
1.A類的a1方法沒有標註@Transactional,a2方法標註@Transactional,在a1裡面呼叫a2;
2.將@Transactional註解標註在非public方法上。

第一種為什麼是錯誤用法,原因很簡單,a1方法是目標類A的原生方法,呼叫a1的時候即直接進入目標類A進行呼叫,在目標類A裡面只有a2的原生方法,在a1裡呼叫a2,即直接執行a2的原生方法,並不通過建立代理物件進行呼叫,所以並不會進入TransactionInterceptor的invoke方法,不會開啟事務。

@Transactional的工作機制是基於AOP實現的,而AOP是使用動態代理實現的,動態代理要麼是JDK方式、要麼是Cglib方式。如果是JDK動態代理的方式,根據上面的分析可以知道,目標類的目標方法是在介面中定義的,也就是必須是public修飾的方法才可以被代理。如果是Cglib方式,代理類是目標類的子類,理論上可以代理public和protected方法,但是Spring在進行事務增強是否能夠應用到當前目標類判斷的時候,遍歷的是目標類的public方法,所以Cglib方式也只對public方法有效。

深入Class類getMethods方法,可以看到取得是public修飾的方法。


相關文章