問:把大象放冰箱裏,分幾步?
答:三步啊,第1、把冰箱門打開,第2、把大象放進去,第3、把冰箱門帶上。
問:實現Spring事務,分幾步?
答:三步啊,第1、找出須要事務的方法,第2、把事務加進去,第3、執行事務。
You may find it's not a joke, it's serious。
java
當你面對一個徹底不熟悉的事物時,必定要想辦法找到一個突破口,而後逐步深刻。那Spring事物的突破口在哪裏呢?很明顯在@EnableTransactionManagement註解裏,由於是它啓用了事物功能。
請看下圖:
編程
發現註解還引入了一個類TransactionManagementConfigurationSelector。
再來看這個類,以下圖:
工具
發現若是採用代理的方式時,又引入了一個類ProxyTransactionManagementConfiguration。this
接着看這個類(3d
重點來了 ),以下圖:結尾說明它是Spring AOP範疇裏的東西。代理
在AOP裏,cdn
Advisor = Pointcut + Advice,Pointcut是切入點,表示要攔截的方法,Advice是加強,表示要加進去的事物功能。對象
再看看另外兩個註冊的bean,就是和這兩個相關的。其中TransactionInterceptor就是一個Advice,由於它實現了Advice接口,包含了把事物加進去的邏輯。blog
TransactionAttributeSource雖然不是一個Pointcut,可是它被Pointcut所用,用於檢測一個類的方法上是否有@Transactional註解,來肯定該方法是否須要事物加強。繼承
從下圖中也能夠看出這一點:
能夠看到這個bean經過下面的set方法被設置進去,而後又用在了Pointcut的類裏了。
總體來看,此部分的結構和功能劃分仍是很是清晰的。下面來逐一研究。
TransactionAttributeSourcePointcut類以Pointcut結尾,說明它是一個切入點,就是標識要被攔截的方法。類名的前綴部分代表了這個切入點的實現原理。
看下這個前綴是TransactionAttributeSource,它以Source結尾,說明它是一個源(即源泉,有向外提供東西的意思)。它的前綴是TransactionAttribute,即事務屬性。
因而可知,這個源能夠向外提供事務屬性,其實就是判斷一個類的方法上是否標有@Transactional註解,若是有的話還能夠獲取這個註解的屬性(即事務屬性)。
總體來講就是,Pointcut攔截住了方法,而後使用這個「源」去方法和類上獲取事務屬性,若是能獲取到,說明此方法須要參與事務,則進行事務加強,反之則不加強。
下面這張圖能夠證實咱們的想法:
能夠看出matches方法的兩個參數就是一個方法(Method)和一個類(Class<?>)。最後從方法和類上獲取事務屬性,再進行是否爲null判斷。
如今這個「源」仍是個黑盒子,下面來揭開它的面紗。它的實現類是AnnotationTransactionAttributeSource,以Annotation開頭,說明是基於註解實現的。
下面圖是它的源碼的一部分:
第一個方法從類上找事務屬性,第二個方法從方法上找事務屬性,它倆都調用了第三個方法來實現。
PS:咱們都知道,方法上的註解優先級高於類上的,是由於找註解時先找方法上的,找不到時再去類上找。因此方法上的優先級高。此部分代碼邏輯在父類裏寫着呢,這裏再也不展現了。第三個方法使用多個事務註解解析器(TransactionAnnotationParser)去解析註解,爲啥是多個解析器呢?由於事務註解不只Spring提供了,Java後來也提供了,就是javax.transaction.Transactional。
Spring對本身註解的解析器實現類是SpringTransactionAnnotationParser,以下圖:
能夠看出使用工具類來讀取註解@Transactional的屬性,而後逐個解析出屬性值並進行類型轉換,接着把這些屬性封裝到一個類裏,這個類其實就是事務屬性,即TransactionAttribute。
這個事務屬性繼承了事務定義接口,事務定義接口咱們應該都很熟悉,以下圖:
,這些註解屬性就是來規定如何參與的。
這個事務屬性TransactionAttribute是個接口,它的實現類在這裏就再也不詳說了。
Advice就是AOP中的加強,TransactionInterceptor實現了Advice接口,因此它就是事務加強。
先來看下該接口,以下圖:
,是一個AOP聯盟組織,它制定的AOP規範。
先來了解下AOP領域的一些相關內容,Pointcut是切入點,表示要攔截的方法。
它是一個靜態的概念 , 即程序不運行時它也是存在的。
那麼在真正運行時,已經攔截住了,此時該怎麼表示這個狀況呢?是用Joinpoint來表示的,因此
Joinpoint是一個運行時的概念 , 只有在運行時才存在。
請看Joinpoint接口,以下圖:
第一個方法proceed()是「繼續」的意思,調用它表示去執行被攔截住的方法自己,返回方法自己的返回值。
第二個方法getThis()是獲取this對象,即方法運行時所在的目標對象。若是是靜態方法,則爲null,由於靜態方法是屬於類自己的,運行時不須要對象。
第三個方法getStaticPart(),其實就表示了被攔截住的方法,即就是一個Method。Method其實算是「元數據」,是屬於類型自己的,也有「靜態」的意思。
再看一個接口,Invocation,它繼承了Joinpoint,以下圖:
方法getArguments()就表示運行時傳遞給被攔截住方法的參數。
再看一個接口,MethodInvocation,它繼承了Invocation,以下圖:
方法getMethod()返回一個Method,它就是當前正在執行的方法,是對本攔截方法的一個友好實現,返回相同的結果。
可見MethodInvocation接口已經包含了一個方法調用的全量信息,
方法 , 參數 , 目標對象。這其實就是運行時被攔截住的東西。
再看下面這個接口,MethodInterceptor,方法攔截器,以下圖:
。
TransactionInterceptor類就實現了這個接口,
所以能夠在對目標方法的調用先後插入事務邏輯代碼來進行事務加強。
下面是事務攔截器對該方法的實現,以下圖:
它調用的invokeWithinTransaction方法是在父類裏的,看下圖:
這個圖裏作的事情較多,逐個來看:
前兩行獲取事務屬性「源」,再用這個「源」來獲取事務屬性。
咦,有點奇怪,上面不是已經獲取過了嗎?是的,上面是在Pointcut裏獲取的,那只是用於判斷那個方法是否要被攔截而已。這裏獲取的屬性纔是真正用於事務的。
第三行是根據事務屬性,來肯定出一個事務管理器來。
接下來是使用事務管理器打開事務。
接下來是對被攔截住的目標方法的調用執行,固然要try/catch住這個執行。
若是拋出了異常,則進行和異常相關的事務處理,而後將這個異常繼續向上拋出。
若是沒有拋出異常,則進行事務提交。
最後的else分支是對編程式事務的調用,事務的打開/提交/回滾是開發人員本身寫代碼控制,因此就不須要事務管理器操心了。
下面請看和異常相關的事務處理,以下圖:
判斷異常類型是否須要回滾,須要的話就回滾事務,不須要的話就繼續提交事務。
這裏的總體結構和邏輯流程也是比較清晰的,那是由於一方面得益於AOP領域的概念,另外一方面是事務管理器屏蔽了事務的全部複雜性。