您好,登錄后才能下訂單哦!
轉自: http://www.linkedkeeper.com/detail/blog.action?bid=1045
Spring事務是我們日常工作中經常使用的一項技術,Spring提供了編程、注解、aop切面三種方式供我們使用Spring事務,其中編程式事務因為對代碼入侵較大所以不被推薦使用,注解和aop切面的方式可以基于需求自行選擇,我們以注解的方式為例來分析Spring事務的原理和源碼實現。
首先我們簡單看一下Spring事務的使用方式,配置:
<tx:annotation-driven transaction-manager="transactionManager"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
在需要開啟事務的方法上加上@Transactional注解即可,這里需要注意的是,當 tx:annotation-driven/標簽在不指定transaction-manager屬性的時候,會默認尋找id固定名為transactionManager的bean作為事務管理器,如果沒有id為transactionManager的bean并且在使用@Transactional注解時也沒有指定value(事務管理器),程序就會報錯。當我們在配置兩個以上的 tx:annotation-driven/標簽時,如下:
<tx:annotation-driven transaction-manager="transactionManager1"/> <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource1"/> </bean> <tx:annotation-driven transaction-manager="transactionManager2"/> <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource2"/> </bean>
這時第一個 tx:annotation-driven/會生效,也就是當我們使用@Transactional注解時不指定事務管理器,默認使用的事務管理器是transactionManager1,后文分析源碼時會具體提到這些注意點。
下面我們開始分析Spring的相關源碼,首先看一下對 tx:annotation-driven/標簽的解析,這里需要讀者對Spring自定義標簽解析的過程有一定的了解,筆者后續也會出相關的文章。鎖定TxNamespaceHandler:
(右鍵可查看大圖)
這個方法比較長,關鍵的部分做了標記,最外圍的if判斷限制了 tx:annotation-driven/標簽只能被解析一次,所以只有第一次被解析的標簽會生效。藍色框的部分分別注冊了三個BeanDefinition,分別為AnnotationTransactionAttributeSource、TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor,并將前兩個BeanDefinition添加到第三個BeanDefinition的屬性當中,這三個bean支撐了整個事務功能,后面會詳細說明。我們先來看紅色框的第個方法:
還記得當 tx:annotation-driven/標簽在不指定transaction-manager屬性的時候,會默認尋找id固定名為transactionManager的bean作為事務管理器這個注意事項么,就是在這里實現的。下面我們來看紅色框的第二個方法:
這兩個方法的主要目的是注冊InfrastructureAdvisorAutoProxyCreator,注冊這個類的目的是什么呢?我們看下這個類的層次:
我們發現這個類間接實現了BeanPostProcessor接口,我們知道,Spring會保證所有bean在實例化的時候都會調用其postProcessAfterInitialization方法,我們可以使用這個方法包裝和改變bean,而真正實現這個方法是在其父類AbstractAutoProxyCreator類中:
上面這個方法相信大家已經看出了它的目的,先找出所有對應Advisor的類的beanName,再通過beanFactory.getBean方法獲取這些bean并返回。不知道大家還是否記得在文章開始的時候提到的三個類,其中BeanFactoryTransactionAttributeSourceAdvisor實現了Advisor接口,所以這個bean就會在此被提取出來,而另外兩個bean被織入了BeanFactoryTransactionAttributeSourceAdvisor當中,所以也會一起被提取出來,下圖為BeanFactoryTransactionAttributeSourceAdvisor類的層次:
下面讓我們來看Spring如何在所有候選的增強器中獲取匹配的增強器:
上面的方法中提到引介增強的概念,在此做簡要說明,引介增強是一種比較特殊的增強類型,它不是在目標方法周圍織入增強,而是為目標類創建新的方法和屬性,所以引介增強的連接點是類級別的,而非方法級別的。通過引介增強,我們可以為目標類添加一個接口的實現,即原來目標類未實現某個接口,通過引介增強可以為目標類創建實現該接口的代理,使用方法可以參考文末的引用鏈接。另外這個方法用兩個重載的canApply方法為目標類尋找匹配的增強器,其中第一個canApply方法會調用第二個canApply方法并將第三個參數傳為false:
在上面BeanFactoryTransactionAttributeSourceAdvisor類的層次中我們看到它實現了PointcutAdvisor接口,所以會調用紅框中的canApply方法進行判斷,第一個參數pca.getPointcut()也就是調用BeanFactoryTransactionAttributeSourceAdvisor的getPointcut方法:
這里的transactionAttributeSource也就是我們在文章開始看到的為BeanFactoryTransactionAttributeSourceAdvisor織入的兩個bean中的AnnotationTransactionAttributeSource,我們以TransactionAttributeSourcePointcut作為第一個參數繼續跟蹤canApply方法:
我們跟蹤pc.getMethodMatcher()方法也就是TransactionAttributeSourcePointcut的getMethodMatcher方法是在它的父類中實現:
發現方法直接返回this,也就是下面methodMatcher.matches方法就是調用TransactionAttributeSourcePointcut的matches方法:
在上面我們看到其實這個tas就是AnnotationTransactionAttributeSource,這里的目的其實也就是判斷我們的業務方法或者類上是否有@Transactional注解,跟蹤AnnotationTransactionAttributeSource的getTransactionAttribute方法:
方法中的事務聲明優先級最高,如果方法上沒有聲明則在類上尋找:
this.annotationParsers是在AnnotationTransactionAttributeSource類初始化的時候初始化的:
所以annotationParser.parseTransactionAnnotation就是調用SpringTransactionAnnotationParser的parseTransactionAnnotation方法:
至此,我們終于看到的Transactional注解,下面無疑就是解析注解當中聲明的屬性了:
在這個方法中我們看到了在Transactional注解中聲明的各種常用或者不常用的屬性的解析,至此,事務的初始化工作算是完成了,下面開始真正的進入執行階段。
在上文AbstractAutoProxyCreator類的wrapIfNecessary方法中,獲取到目標bean匹配的增強器之后,會為bean創建代理,這部分內容我們會在Spring AOP的文章中進行詳細說明,在此簡要說明方便大家理解,在執行代理類的目標方法時,會調用Advisor的getAdvice獲取MethodInterceptor并執行其invoke方法,而我們本文的主角BeanFactoryTransactionAttributeSourceAdvisor的getAdvice方法會返回我們在文章開始看到的為其織入的另外一個bean,也就是TransactionInterceptor,它實現了MethodInterceptor,所以我們分析其invoke方法:
這個方法很長,但是整體邏輯還是非常清晰的,首選獲取事務屬性,這里的getTransactionAttrubuteSource()方法的返回值同樣是在文章開始我們看到的被織入到TransactionInterceptor中的AnnotationTransactionAttributeSource,在事務準備階段已經解析過事務屬性并保存到緩存中,所以這里會直接從緩存中獲取,接下來獲取配置的TransactionManager,也就是determineTransactionManager方法,這里如果配置沒有指定transaction-manager并且也沒有默認id名為transactionManager的bean,就會報錯,然后是針對聲明式事務和編程式事務的不同處理,創建事務信息,執行目標方法,最后根據執行結果進行回滾或提交操作,我們先分析創建事務的過程。在分析之前希望大家能先去了解一下Spring的事務傳播行為,有助于理解下面的源碼,這里做一個簡要的介紹,更詳細的信息請大家自行查閱Spring官方文檔,里面有更新詳細的介紹。
Spring的事務傳播行為定義在Propagation這個枚舉類中,一共有七種,分別為:
REQUIRED:業務方法需要在一個容器里運行。如果方法運行時,已經處在一個事務中,那么加入到這個事務,否則自己新建一個新的事務,是默認的事務傳播行為。
NOT_SUPPORTED:聲明方法不需要事務。如果方法沒有關聯到一個事務,容器不會為他開啟事務,如果方法在一個事務中被調用,該事務會被掛起,調用結束后,原先的事務會恢復執行。
REQUIRESNEW:不管是否存在事務,該方法總匯為自己發起一個新的事務。如果方法已經運行在一個事務中,則原有事務掛起,新的事務被創建。
MANDATORY:該方法只能在一個已經存在的事務中執行,業務方法不能發起自己的事務。如果在沒有事務的環境下被調用,容器拋出例外。
SUPPORTS:該方法在某個事務范圍內被調用,則方法成為該事務的一部分。如果方法在該事務范圍外被調用,該方法就在沒有事務的環境下執行。
NEVER:該方法絕對不能在事務范圍內執行。如果在就拋例外。只有該方法沒有關聯到任何事務,才正常執行。
NESTED:如果一個活動的事務存在,則運行在一個嵌套的事務中。如果沒有活動事務,則按REQUIRED屬性執行。它使用了一個單獨的事務,這個事務擁有多個可以回滾的保存點。內部事務的回滾不會對外部事務造成影響。它只對DataSourceTransactionManager事務管理器起效。
判斷當前線程是否存在事務就是判斷記錄的數據庫連接是否為空并且transactionActive狀態為true。
REQUIRESNEW會開啟一個新事務并掛起原事務,當然開啟一個新事務就需要一個新的數據庫連接:
suspend掛起操作主要目的是將當前connectionHolder置為null,保存原有事務信息,以便于后續恢復原有事務,并將當前正在進行的事務信息進行重置。下面我們看Spring如何開啟一個新事務:
這里我們看到了數據庫連接的獲取,如果是新事務需要獲取新一個新的數據庫連接,并為其設置了隔離級別、是否只讀等屬性,下面就是將事務信息記錄到當前線程中:
接下來就是記錄事務狀態并返回事務信息:
然后就是我們目標業務方法的執行了,根據執行結果的不同做提交或回滾操作,我們先看一下回滾操作:
其中回滾條件默認為RuntimeException或Error,我們也可以自行配置。
保存點一般用于嵌入式事務,內嵌事務的回滾不會引起外部事務的回滾。下面我們來看新事務的回滾:
很簡單,就是獲取當前線程的數據庫連接并調用其rollback方法進行回滾,使用的是底層數據庫連接提供的API。最后還有一個清理和恢復掛起事務的操作:
如果事務執行前有事務掛起,那么當前事務執行結束后需要將掛起的事務恢復,掛起事務時保存了原事務信息,重置了當前事務信息,所以恢復操作就是將當前的事務信息設置為之前保存的原事務信息。到這里事務的回滾操作就結束了,下面讓我們來看事務的提交操作:
在上文分析回滾流程中我們提到了如果當前事務不是獨立的事務,也沒有保存點,在回滾的時候只是設置一個回滾標記,由外部事務提交時統一進行整體事務的回滾。
提交操作也是很簡單的調用數據庫連接底層API的commit方法。
參考鏈接:
http://blog.163.com/asd_wll/blog/static/2103104020124801348674/
https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#spring-data-tier
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。