天天看點

Spring源碼剖析9:Spring事務源碼剖析

轉自:http://www.linkedkeeper.com/detail/blog.action?bid=1045

本系列文章将整理到我在GitHub上的《Java面試指南》倉庫,更多精彩内容請到我的倉庫裡檢視

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章将同步到我的個人部落格:

www.how2playlife.com

本文是微信公衆号【Java技術江湖】的《Spring和SpringMVC源碼分析》其中一篇,本文部分内容來源于網絡,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格内容,引用其中了一些比較好的部落格文章,如有侵權,請聯系作者。

該系列博文會告訴你如何從spring基礎入手,一步步地學習spring基礎和springmvc的架構知識,并上手進行項目實戰,spring架構是每一個Java工程師必須要學習和了解的知識點,進一步來說,你還需要掌握spring甚至是springmvc的源碼以及實作原理,才能更完整地了解整個spring技術體系,形成自己的知識架構。

後續還會有springboot和springcloud的技術專題,陸續為大家帶來,敬請期待。

為了更好地總結和檢驗你的學習成果,本系列文章也會提供部分知識點對應的面試題以及參考答案。

如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公衆号【Java技術江湖】聯系作者,歡迎你參與本系列博文的創作和修訂。

<!-- more -->

聲明式事務使用

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></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,後文分析源碼時會具體提到這些注意點。</tx:annotation-driven>

下面我們開始分析Spring的相關源碼,首先看一下對<tx:annotation-driven>标簽的解析,這裡需要讀者對Spring自定義标簽解析的過程有一定的了解,筆者後續也會出相關的文章。鎖定TxNamespaceHandler:</tx:annotation-driven>

TxNamespaceHandler

(右鍵可檢視大圖)

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

注冊事務功能bean

這個方法比較長,關鍵的部分做了标記,最外圍的if判斷限制了<tx:annotation-driven>标簽隻能被解析一次,是以隻有第一次被解析的标簽會生效。藍色框的部分分别注冊了三個BeanDefinition,分别為AnnotationTransactionAttributeSource、TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor,并将前兩個BeanDefinition添加到第三個BeanDefinition的屬性當中,這三個bean支撐了整個事務功能,後面會詳細說明。我們先來看紅色框的第個方法:</tx:annotation-driven>

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

還記得當<tx:annotation-driven>标簽在不指定transaction-manager屬性的時候,會預設尋找id固定名為transactionManager的bean作為事務管理器這個注意事項麼,就是在這裡實作的。下面我們來看紅色框的第二個方法:</tx:annotation-driven>

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

這兩個方法的主要目的是注冊InfrastructureAdvisorAutoProxyCreator,注冊這個類的目的是什麼呢?我們看下這個類的層次:

Spring源碼剖析9:Spring事務源碼剖析

使用bean的後處理方法擷取增強器

我們發現這個類間接實作了BeanPostProcessor接口,我們知道,Spring會保證所有bean在執行個體化的時候都會調用其postProcessAfterInitialization方法,我們可以使用這個方法包裝和改變bean,而真正實作這個方法是在其父類AbstractAutoProxyCreator類中:

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

上面這個方法相信大家已經看出了它的目的,先找出所有對應Advisor的類的beanName,再通過beanFactory.getBean方法擷取這些bean并傳回。不知道大家還是否記得在文章開始的時候提到的三個類,其中BeanFactoryTransactionAttributeSourceAdvisor實作了Advisor接口,是以這個bean就會在此被提取出來,而另外兩個bean被織入了BeanFactoryTransactionAttributeSourceAdvisor當中,是以也會一起被提取出來,下圖為BeanFactoryTransactionAttributeSourceAdvisor類的層次:

Spring源碼剖析9:Spring事務源碼剖析

Spring擷取比對的增強器

下面讓我們來看Spring如何在所有候選的增強器中擷取比對的增強器:

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

上面的方法中提到引介增強的概念,在此做簡要說明,引介增強是一種比較特殊的增強類型,它不是在目标方法周圍織入增強,而是為目标類建立新的方法和屬性,是以引介增強的連接配接點是類級别的,而非方法級别的。通過引介增強,我們可以為目标類添加一個接口的實作,即原來目标類未實作某個接口,通過引介增強可以為目标類建立實作該接口的代理,使用方法可以參考文末的引用連結。另外這個方法用兩個重載的canApply方法為目标類尋找比對的增強器,其中第一個canApply方法會調用第二個canApply方法并将第三個參數傳為false:

Spring源碼剖析9:Spring事務源碼剖析

在上面BeanFactoryTransactionAttributeSourceAdvisor類的層次中我們看到它實作了PointcutAdvisor接口,是以會調用紅框中的canApply方法進行判斷,第一個參數pca.getPointcut()也就是調用BeanFactoryTransactionAttributeSourceAdvisor的getPointcut方法:

Spring源碼剖析9:Spring事務源碼剖析

這裡的transactionAttributeSource也就是我們在文章開始看到的為BeanFactoryTransactionAttributeSourceAdvisor織入的兩個bean中的AnnotationTransactionAttributeSource,我們以TransactionAttributeSourcePointcut作為第一個參數繼續跟蹤canApply方法:

Spring源碼剖析9:Spring事務源碼剖析

我們跟蹤pc.getMethodMatcher()方法也就是TransactionAttributeSourcePointcut的getMethodMatcher方法是在它的父類中實作:

Spring源碼剖析9:Spring事務源碼剖析

發現方法直接傳回this,也就是下面methodMatcher.matches方法就是調用TransactionAttributeSourcePointcut的matches方法:

Spring源碼剖析9:Spring事務源碼剖析

在上面我們看到其實這個tas就是AnnotationTransactionAttributeSource,這裡的目的其實也就是判斷我們的業務方法或者類上是否有@Transactional注解,跟蹤AnnotationTransactionAttributeSource的getTransactionAttribute方法:

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

方法中的事務聲明優先級最高,如果方法上沒有聲明則在類上尋找:

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

this.annotationParsers是在AnnotationTransactionAttributeSource類初始化的時候初始化的:

Spring源碼剖析9:Spring事務源碼剖析

是以annotationParser.parseTransactionAnnotation就是調用SpringTransactionAnnotationParser的parseTransactionAnnotation方法:

Spring源碼剖析9:Spring事務源碼剖析

至此,我們終于看到的Transactional注解,下面無疑就是解析注解當中聲明的屬性了:

Transactional注解

Spring源碼剖析9:Spring事務源碼剖析

在這個方法中我們看到了在Transactional注解中聲明的各種常用或者不常用的屬性的解析,至此,事務的初始化工作算是完成了,下面開始真正的進入執行階段。

在上文AbstractAutoProxyCreator類的wrapIfNecessary方法中,擷取到目标bean比對的增強器之後,會為bean建立代理,這部分内容我們會在Spring AOP的文章中進行詳細說明,在此簡要說明友善大家了解,在執行代理類的目标方法時,會調用Advisor的getAdvice擷取MethodInterceptor并執行其invoke方法,而我們本文的主角BeanFactoryTransactionAttributeSourceAdvisor的getAdvice方法會傳回我們在文章開始看到的為其織入的另外一個bean,也就是TransactionInterceptor,它實作了MethodInterceptor,是以我們分析其invoke方法:

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

這個方法很長,但是整體邏輯還是非常清晰的,首選擷取事務屬性,這裡的getTransactionAttrubuteSource()方法的傳回值同樣是在文章開始我們看到的被織入到TransactionInterceptor中的AnnotationTransactionAttributeSource,在事務準備階段已經解析過事務屬性并儲存到緩存中,是以這裡會直接從緩存中擷取,接下來擷取配置的TransactionManager,也就是determineTransactionManager方法,這裡如果配置沒有指定transaction-manager并且也沒有預設id名為transactionManager的bean,就會報錯,然後是針對聲明式事務和程式設計式事務的不同處理,建立事務資訊,執行目标方法,最後根據執行結果進行復原或送出操作,我們先分析建立事務的過程。在分析之前希望大家能先去了解一下Spring的事務傳播行為,有助于了解下面的源碼,這裡做一個簡要的介紹,更詳細的資訊請大家自行查閱Spring官方文檔,裡面有更新詳細的介紹。

Spring的事務傳播行為定義在Propagation這個枚舉類中,一共有七種,分别為:

REQUIRED:業務方法需要在一個容器裡運作。如果方法運作時,已經處在一個事務中,那麼加入到這個事務,否則自己建立一個新的事務,是預設的事務傳播行為。

NOT_SUPPORTED:聲明方法不需要事務。如果方法沒有關聯到一個事務,容器不會為他開啟事務,如果方法在一個事務中被調用,該事務會被挂起,調用結束後,原先的事務會恢複執行。

REQUIRESNEW:不管是否存在事務,該方法總彙為自己發起一個新的事務。如果方法已經運作在一個事務中,則原有事務挂起,新的事務被建立。

MANDATORY:該方法隻能在一個已經存在的事務中執行,業務方法不能發起自己的事務。如果在沒有事務的環境下被調用,容器抛出例外。

SUPPORTS:該方法在某個事務範圍内被調用,則方法成為該事務的一部分。如果方法在該事務範圍外被調用,該方法就在沒有事務的環境下執行。

NEVER:該方法絕對不能在事務範圍内執行。如果在就抛例外。隻有該方法沒有關聯到任何事務,才正常執行。

NESTED:如果一個活動的事務存在,則運作在一個嵌套的事務中。如果沒有活動事務,則按REQUIRED屬性執行。它使用了一個單獨的事務,這個事務擁有多個可以復原的儲存點。内部事務的復原不會對外部事務造成影響。它隻對DataSourceTransactionManager事務管理器起效。

開啟事務過程

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

判斷目前線程是否存在事務就是判斷記錄的資料庫連接配接是否為空并且transactionActive狀态為true。

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

REQUIRESNEW會開啟一個新事務并挂起原事務,當然開啟一個新事務就需要一個新的資料庫連接配接:

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

suspend挂起操作主要目的是将目前connectionHolder置為null,儲存原有事務資訊,以便于後續恢複原有事務,并将目前正在進行的事務資訊進行重置。下面我們看Spring如何開啟一個新事務:

Spring源碼剖析9:Spring事務源碼剖析

這裡我們看到了資料庫連接配接的擷取,如果是新事務需要擷取新一個新的資料庫連接配接,并為其設定了隔離級别、是否隻讀等屬性,下面就是将事務資訊記錄到目前線程中:

Spring源碼剖析9:Spring事務源碼剖析

接下來就是記錄事務狀态并傳回事務資訊:

Spring源碼剖析9:Spring事務源碼剖析

然後就是我們目标業務方法的執行了,根據執行結果的不同做送出或復原操作,我們先看一下復原操作:

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

其中復原條件預設為RuntimeException或Error,我們也可以自行配置。

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

儲存點一般用于嵌入式事務,内嵌事務的復原不會引起外部事務的復原。下面我們來看新事務的復原:

Spring源碼剖析9:Spring事務源碼剖析

很簡單,就是擷取目前線程的資料庫連接配接并調用其rollback方法進行復原,使用的是底層資料庫連接配接提供的API。最後還有一個清理和恢複挂起事務的操作:

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

如果事務執行前有事務挂起,那麼目前事務執行結束後需要将挂起的事務恢複,挂起事務時儲存了原事務資訊,重置了目前事務資訊,是以恢複操作就是将目前的事務資訊設定為之前儲存的原事務資訊。到這裡事務的復原操作就結束了,下面讓我們來看事務的送出操作:

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

在上文分析復原流程中我們提到了如果目前事務不是獨立的事務,也沒有儲存點,在復原的時候隻是設定一個復原标記,由外部事務送出時統一進行整體事務的復原。

Spring源碼剖析9:Spring事務源碼剖析
Spring源碼剖析9:Spring事務源碼剖析

送出操作也是很簡單的調用資料庫連接配接底層API的commit方法。