來自:程式設計新說
Starting from a joke
問:把大象放冰箱裡,分幾步?
答:三步啊,第一、把冰箱門開啟,第二、把大象放進去,第三、把冰箱門帶上。
問:實現Spring事務,分幾步?
答:三步啊,第一、找出需要事務的方法,第二、把事務加進去,第三、執行事務。
You may find it’s not a joke, it’s serious。
Try to find an entrance
當你面對一個完全不熟悉的事物時,一定要想辦法找到一個突破口,然後逐步深入。那Spring事物的突破口在哪裡呢?很明顯在@EnableTransactionManagement註解裡,因為是它啟用了事物功能。
請看下圖:
發現註解還引入了一個類TransactionManagementConfigurationSelector。
再來看這個類,如下圖:
發現如果採用代理的方式時,又引入了一個類ProxyTransactionManagementConfiguration。
接著看這個類(重點來了),如下圖:
發現這個類往容器中註冊了3個bean,第一個是BeanFactoryTransactionAttributeSourceAdvisor。它以Advisor結尾說明它是Spring AOP範疇裡的東西。
在AOP裡,Advisor = Pointcut + Advice,Pointcut是切入點,表示要攔截的方法,Advice是增強,表示要加進去的事物功能。
再看看另外兩個註冊的bean,就是和這兩個相關的。其中TransactionInterceptor就是一個Advice,因為它實現了Advice介面,包含了把事物加進去的邏輯。
TransactionAttributeSource雖然不是一個Pointcut,但是它被Pointcut所用,用於檢測一個類的方法上是否有@Transactional註解,來確定該方法是否需要事物增強。
從下圖中也可以看出這一點:
可以看到這個bean透過下麵的set方法被設定進去,然後又用在了Pointcut的類裡了。
整體來看,此部分的結構和功能劃分還是非常清晰的。下麵來逐一研究。
AOP切點
TransactionAttributeSourcePointcut類以Pointcut結尾,說明它是一個切入點,就是標識要被攔截的方法。類名的字首部分表明瞭這個切入點的實現原理。
看下這個字首是TransactionAttributeSource,它以Source結尾,說明它是一個源(即源泉,有向外提供東西的意思)。它的字首是TransactionAttribute,即事務屬性。
由此可見,這個源可以向外提供事務屬性,其實就是判斷一個類的方法上是否標有@Transactional註解,如果有的話還可以獲取這個註解的屬性(即事務屬性)。
整體來說就是,Pointcut攔截住了方法,然後使用這個“源”去方法和類上獲取事務屬性,如果能獲取到,說明此方法需要參與事務,則進行事務增強,反之則不增強。
下麵這張圖可以證明我們的想法:
可以看出matches方法的兩個引數就是一個方法(Method)和一個類(Class >)。最後從方法和類上獲取事務屬性,再進行是否為null判斷。
現在這個“源”還是個黑盒子,下麵來揭開它的面紗。它的實現類是AnnotationTransactionAttributeSource,以Annotation開頭,說明是基於註解實現的。
下麵圖是它的原始碼的一部分:
第一個方法從類上找事務屬性,第二個方法從方法上找事務屬性,它倆都呼叫了第三個方法來實現。
PS:我們都知道,方法上的註解優先順序高於類上的,是因為找註解時先找方法上的,找不到時再去類上找。所以方法上的優先順序高。此部分程式碼邏輯在父類裡寫著呢,這裡不再展示了。
第三個方法使用多個事務註解解析器(TransactionAnnotationParser)去解析註解,為啥是多個解析器呢?因為事務註解不僅Spring提供了,Java後來也提供了,就是javax.transaction.Transactional。
Spring對自己註解的解析器實現類是SpringTransactionAnnotationParser,如下圖:
可以看出使用工具類來讀取註解@Transactional的屬性,然後逐個解析出屬性值併進行型別轉換,接著把這些屬性封裝到一個類裡,這個類其實就是事務屬性,即TransactionAttribute。
這個事務屬性繼承了事務定義介面,事務定義介面我們應該都很熟悉,如下圖:
這也證明瞭以前文章裡說過的話,@Transactional註解的作用有兩個,一是表明要參與事務,二是表明如何參與事務,這些註解屬性就是來規定如何參與的。
這個事務屬性TransactionAttribute是個介面,它的實現類在這裡就不再詳說了。
AOP增強
Advice就是AOP中的增強,TransactionInterceptor實現了Advice介面,所以它就是事務增強。
先來看下該介面,如下圖:
發現它只是一個空的標記介面。而且它的包名是org.aopalliance,是一個AOP聯盟組織,它制定的AOP規範。
先來瞭解下AOP領域的一些相關內容,Pointcut是切入點,表示要攔截的方法。它是一個靜態的概念,即程式不執行時它也是存在的。
那麼在真正執行時,已經攔截住了,此時該怎麼表示這個情況呢?是用Joinpoint來表示的,所以Joinpoint是一個執行時的概念,只有在執行時才存在。
請看Joinpoint介面,如下圖:
第一個方法proceed()是“繼續”的意思,呼叫它表示去執行被攔截住的方法本身,傳回方法本身的傳回值。
第二個方法getThis()是獲取this物件,即方法執行時所在的標的物件。如果是靜態方法,則為null,因為靜態方法是屬於類本身的,執行時不需要物件。
第三個方法getStaticPart(),其實就表示了被攔截住的方法,即就是一個Method。Method其實算是“元資料”,是屬於型別本身的,也有“靜態”的意思。
再看一個介面,Invocation,它繼承了Joinpoint,如下圖:
方法getArguments()就表示執行時傳遞給被攔截住方法的引數。
再看一個介面,MethodInvocation,它繼承了Invocation,如下圖:
方法getMethod()傳回一個Method,它就是當前正在執行的方法,是對本攔截方法的一個友好實現,傳回相同的結果。
可見MethodInvocation介面已經包含了一個方法呼叫的全量資訊,方法,引數,標的物件。這其實就是執行時被攔截住的東西。
再看下麵這個介面,MethodInterceptor,方法攔截器,如下圖:
它只有一個方法invoke,方法引數就是上面介紹的MethodInvocation。所以攔截器可以使用這個引數來對標的方法進行呼叫,當然在呼叫前/後可以加入自己的邏輯。
TransactionInterceptor類就實現了這個介面,因此可以在對標的方法的呼叫前後插入事務邏輯程式碼來進行事務增強。
下麵是事務攔截器對該方法的實現,如下圖:
它呼叫的invokeWithinTransaction方法是在父類裡的,看下圖:
這個圖裡做的事情較多,逐個來看:
前兩行獲取事務屬性“源”,再用這個“源”來獲取事務屬性。咦,有點奇怪,上面不是已經獲取過了嗎?是的,上面是在Pointcut裡獲取的,那隻是用於判斷那個方法是否要被攔截而已。這裡獲取的屬性才是真正用於事務的。
第三行是根據事務屬性,來確定出一個事務管理器來。
接下來是使用事務管理器開啟事務。
接下來是對被攔截住的標的方法的呼叫執行,當然要try/catch住這個執行。
如果丟擲了異常,則進行和異常相關的事務處理,然後將這個異常繼續向上丟擲。
如果沒有丟擲異常,則進行事務提交。
最後的else分支是對程式設計式事務的呼叫,事務的開啟/提交/回滾是開發人員自己寫程式碼控制,所以就不需要事務管理器操心了。
下麵請看和異常相關的事務處理,如下圖:
判斷異常型別是否需要回滾,需要的話就回滾事務,不需要的話就繼續提交事務。
這裡的整體結構和邏輯流程也是比較清晰的,那是因為一方面得益於AOP領域的概念,另一方面是事務管理器遮蔽了事務的所有複雜性。
PS:事務管理器的內容其實還是挺複雜的,下篇文章再詳細解說。