ABP以AOP的方式實現UnitOfWork功能。通過UnitOfWorkRegistrar將UnitOfWorkInterceptor在某個類被注冊到IOCContainner的時候,一並添加到該類在容器中對應的ComponentModel的Interceptors集合中。總結一句話就是,UOW的功能是通過自定義Castle攔截器來實現的。本文主要介紹ABP核心框架中的UnitOfWork的實現,后續會分別介紹ABP其他模塊是如何具體實現IUnitOfWork的
如圖,AbpKernelModule調用UnitOfWorkRegister的Initialize方法將UnitOfWorkInterceptor攔截器添加到標注了UnitOfWork特性方法的類,以及實現IRepository或IApplicationService的類上。
下圖是UnitOfWorkRegister的代碼
和通常實現的AOP一樣,ABP定義了UnitOfWork特性,方便業務層為方法注入UOW功能。
UnitOfWorkAttribute:用於標注某個方法位UnitOfWork的Attribute類. 通過這個特性,可以指定是否啟用UOW,事務隔離級別,TransactionScopeOption等
UnitOfWorkOptions: 封裝了UnitOfWork參數的類,其實例是通過UnitOfWorkAttribute的CreateOptions來生成的。
接下來,分析下UnitOfWork是如何封裝事務的。
基於接口隔離原則的考量,ABP作者將UnitOfWork的方法分到了三個不同的接口中,如下圖。
IUnitOfWorkCompleteHandle:定義了UOW同步和異步的complete方法。實現UOW完成時候的邏輯。
IActiveUnitOfWork:一個UOW除了以上兩個接口中定義的方法和屬性外,其他的屬性和方法都在這個接口定義的。比如Completed,Disposed,Failed事件代理,Filter的enable和disable,以及同步、異步的SaveChanges方法。
IUnitOfWork:繼承了上面兩個接口。定義了外層的IUnitOfWork的引用和UOW的begin方法。 ABP是通過構建一個UnitOfWork的鏈,將不同的方法納入到一個事務中(后文解釋)。
UnitOfWorkBase:這個抽象類實現了上面三個接口中定義的方法,而真正實現事務控制的方法是由這個抽象類的子類實現的(比如,真正創建TransactionScope的操作是在EfUnitOfWork,NhUnitOfWork這樣的之類中實現的)。UOW中除了事務控制邏輯以外的邏輯都是由UnitOfWorkBase抽象類實現的。
ABP中共有以下4個具體的UOW類型,他們都繼承自UnitOfWorkBase。Entity Framework, Nhibernate UnitOfWork是實現事務控制的UOW。MongoDB 和 MemoryDB UnitOfWork是沒有事務控制的。 原因很簡單,MongoDB本身就沒有完整的事務控制功能, 而ABP 框架實現的MemoryDB也是沒有事務控制功能的。
IUnitOfWorkManager/UnitOfWorkManager:和其他***manager類一樣是一個Facade,他對外提供UOW的功能(用於創建UnitOfWork,並開啟UnitOfWork流程),對內調用各種UOW功能的各種組件。
UnitOfWork攔截器調用UnitOfWorkManager開啟UOW流程的代碼。
Unit Of Work大致運行流程如下
- UOW攔截器被注入到需要UOW的類中。
- ABP執行標注了UnitOfWork特性的方法時。UOW攔截器以begin()->realAction()->complete()->dispose()順序執行,其中realAction是被調用的真實業務操作。 UOW攔截器是通過using這種方式調用IUnitOfWork的某個具體實現,這就確保begin 和 dispose也總是會被執行的。 這里需要注意complete卻不一定會被執行,比如在complete方法被調用前方法的執行產生了異常。
- 當執行一連串的操作時(A方法->B方法->C方法,假設這三個方法都標注了UnitOfWork特性),ABP在執行A方法前會創建整個過程中唯一的IUnitOfWork對象,該對象會啟動.NET事務。在執行到B,C方法只會創建InnerUnitOfWorkCompleteHandle。 InnerUnitOfWorkCompleteHandle與IUnitOfWork對象的差異在於它不會創建真實的事務。但ABP會調用其complete,以告知ABP其對應的方法以成功完成,可以提交事務.
- 事務可以回滾的關鍵關鍵在於IUnitOfWork對象在被dispose時候會檢查complete方法有沒有被執行,沒有的話就認為這個UOW標注的方法沒有順利完成,從而導致事務的回滾操作。
- 整個事務的提交是通過第一個UOW(也是唯一個)的complete方法執行時提交的。
InnerUnitOfWorkCompleteHandle關於檢查complete方法有沒有被執行的代碼
UnitOfWorkManager的beign方法。這邊可以看出只有一個IUnitOfWork對象會被創建, 而且由於這個對象是通過容器直接resolve的,那么ABP怎么知道該通過resolve得到什么樣的實例呢?是EfUnitOfWork?還是MongoDbUnitOfWork?還是MemoryDbUnitOfWork?還是NhUnitOfWork?答案是ABP不知道,ABP作者假設你只會用其中一個模塊,所以如果你把這四個module都加入到你的項目中,結果是不可預知的。
EfUnitOfWork在begin方法中創建.NET事務
EfUnitOfWork提交事務
CallContextCurrentUnitOfWorkProvider
CallContextCurrentUnitOfWorkProvider的主要功能其實只有一個:通過current返回當前UOW環境下的UOW實例。
一般思路是:將IUnitOfWork對象定義為實例變量或者是類變量。 但是兩者事實上都不可行。
如果定義為類變量,那就會面臨線程安全的問題,解決方式無非加鎖,但會導致並發能力下降,ABP是web框架,因為鎖導致並發能力下降是不能接受的。
如果定義為實例變量,在同一線程其他地方resolve CallContextCurrentUnitOfWorkProvider這個實例的時候都會得到一個新的實例,新的實例下current自然是NULL.
ABP的做法是:線程邏輯上下文+線程安全的Dictinoray容器。
線程邏輯上下文用於存儲UOW實例的key, 而線程邏輯上下文對於本線程是全局可訪問的,而同時具有天然的隔離性。這就確保了當前線程的各個地方都可以得到current的UOW的key
線程安全的Dictinoray容器是一個類實例,用於存放UOW的實例,通過UOW的key就可以取到UOW的實例。