1. 程式人生 > >abp vnext2.0核心元件之模組載入元件原始碼解析

abp vnext2.0核心元件之模組載入元件原始碼解析

abp vnext是abp官方在abp的基礎之上構建的微服務框架,說實話,看完核心元件原始碼的時候,很興奮,整個框架將元件化的細想運用的很好,真的超級解耦.老版整個框架依賴Castle的問題,vnext對其進行了解耦,支援AutoFac或者使用.Net Core的預設容器.vnext依然沿用EF core為主,其餘ORM為輔助的思想,當然EF core來實現DDD確實有優勢,EventBus提供了分散式版本,並提供了RabbitMQ的實現版本,Aop攔截器依然採用Castle.Core.AsyncInterceptor.這一點Dora.Interception貌似可以解決,估計如果高度元件化,那麼這也是一個擴充套件點.整個模組載入系統更加的完善,提供了跟多可選擇的特性,工作單元也進行了小幅度的重構,程式碼更加的通俗易懂(在實現非同步工作單元巢狀的設計就有體現)等等還有很多,當然不是本文的重點,vnext2.0是個值得使用的框架.下面開始回到正題.

1、模組載入系統

模組載入系統算是vnext的整個框架的入口,離了他,這個框架就廢了.具體它有什麼作用,看下面的程式碼分析,模組載入系統的入口如下:

 每個應用框架必須要有一個啟動模組型別,可以通過泛型或者Type例項傳入,並且給定啟動引數.

啟動模組型別:雖然上面給定的約束是必須實現IAbpModule,但是大多數的實現情況是

暫時不講 AbpModule的原始碼,後面分析到具體的流程再做介紹.

ok,看看AbpApplicationFactory工廠做了什麼了,通過名字分析很明顯.AbpApplication的工廠.

分析這個方法就能得出,只要傳入啟動模組的型別和DI的ServiceCollection和啟動應用的引數,就能構建一個IAbpApplicationWithExternalServiceProvider,那麼看看IAbpApplicationWithExternalServiceProvider都有什麼

 構建完成基本的實體後,呼叫Initialize方法初始化框架.再看看IAbpApplication介面

 包含啟動模組型別,DI注入集合、DI服務提供類,以及一個關閉應用程式必須執行的ShutDown方法.在看看IModuleContainer

 包含模組集合,在Abp中,模組代表一個程式集.這裡就是啟動abp vnext框架的啟動模組型別所依賴的所有模組型別,即所有的程式集集合你可以這樣理解.因為一個Module型別(繼承AbpModule型別或者實現IAbpModule介面的型別)代表一個程式集.且一個程式集只有一個Module型別(繼承AbpModule型別或者實現IAbpModule介面的型別).

ok,接著回到上面的程式碼

 此處省略一些無關核心流程的程式碼,程式碼如下:

 簡單的一些非空校驗,這裡有一個非常有趣的設計,如下:

繼續檢視,如下

 

 ObjectAccessor原始碼如下:

 類似裝飾者模式,內部容納一個型別.最後

 ok,到這裡整個流程大致就是,給IServiceProvider建立一個ObjectAccessor,且ObjectAccessor沒有Value值,同時將ObjectAccessor寫入DI,並做了簡單的搜尋優化.關於IServiceProvider的ObjectAccessor的作用,暫時不介紹,後續會說.

接著看如下程式碼:

 初始化外部設定引數,接招向DI中注入IAbpApplication和IModuleContainer的單例物件.

接著看下面的程式碼:

 

注入配置檔案、日誌、國際化等服務.接著看AddCoeAbpServices方法

 

注入ModuleLoader(處理程式集間依賴關係,處理模組載入生命週期、的核心型別)、程式集發現類(所有程式集都能通過該型別拿到,只要程式集加入到了框架)、型別發現類(程式集集合所包含的所有型別)

 初始化配置檔案系統、等等操作,接著看如下程式碼,將上述型別寫入DI

 接下去這行程式碼就有趣了,如下:

看看它幹了什麼,如下:

 看看 services.GetConventionalRegistrars幹了什麼,如下:

 很明顯,從DI中讀取程式集註冊規則類列表,如果沒有,則寫入預設的程式集註冊規則類.所以,這裡如果你想自定義程式集註冊規則,那麼只需在有效的應用程式載入生命週期階段注入自定義的程式集註冊類即可,該型別必須實現下圖所示介面

 ok,這個擴充套件點講完之後,看看預設的程式集註冊規則類DefaultConventionalRegistrar幹了什麼,如下:

 

 很簡單,自行閱讀,再看看AddType的實現,如下:

 

 支援型別跳過,如果型別打了DisableConventionalRegistrationAttribute特性,那麼該型別將不會被寫入DI.

 如果當前型別沒有打DependencyAttribute,或者打了DependencyAttribute特性,沒有設定Lifetime,則當前型別也不會寫入DI.

這裡注意,根據程式碼可以發現,abp給型別生命週期的方式有兩種,老版只有一種,如下:

第一種:

 

 

 通過實現ISingletonDependency(單例注入),ITransientDependency(普通引用型別),IScopedDependency(範圍內唯一)三大介面來表示當前型別的生命週期,老版abp也是使用這種方式,但是沒有IScopedDependency

第二種:

 

 

 通過DependencyAttribute特性,結構如下

接著,如下程式碼

 如果當前型別打了ExposeServicesAttribute特性,那麼則會呼叫該特性的如下方法

 

 這個方法的用途是找出如果我們需要從DI中釋出個型別,可以使用哪幾種方式(常用的是介面,自身等),示例程式碼如下:

 那麼如果需要在框架中使用TestClass的實現,可以用ITestClass介面進行依賴注入,因為

 當然這裡可以寫多個,因為

 ExposeServicesAttribute特性中的IncludeDefaults和IncludeSelf屬性是預設的策略,

IncludeDefaults設定為true是根據型別找出其實現的介面,且介面必須以I字母開頭,且介面後面的名字必須和當前型別相等.如果匹配那麼該介面有效,也可以進行依賴注入.

IncludeSelf設定為true,則可以通過當前型別進行依賴注入.

接著看如下程式碼

 

 很簡單,只需在有效的應用程式載入生命週期階段注入指定的Action,注入方式如下:

使用例子,型別對映,如下:

最後看如下程式碼

這段程式碼很簡單,就不解釋了.DependencyAttribute特性給上對應的值就能執行指定的操作,ok,到這裡總結一下這種設計的用處,非常nice,原先老版abp註冊系統核心單例型別是依賴castle的,如果換成這種設計方式,更加的靈活,如果我們需要給底層新增一個核心類,只需要建立一個類,然後配合Dependency特性和ExposeServices特性即可和DI完美集合,同時還提供了Action擴充套件,讓你可以幹很多的事情,就這一點,比老版abp好太多.到這裡DefaultConventionalRegistrar介紹完畢

ok,在回到AddCoreAbpServices方法,如下:

 這裡也很簡單,向DI中預先寫入AbpModuleLifecycleOptions,該引數用於控制模組載入的生命週期,這四個Contributor分別對應模組載入生命週期的介面,

 

 

 

 再看看核心Module的抽象

 到這裡肯定很多人很困惑,所以這裡跳過一些流程,看下ModuleManager如何處理,如下

 釋出Contributor集合

 

Contributor的作用很明顯,模組載入生命週期中你可以執行的一些方法.這些方法會拿到一個ServiceProvider,即你可以操作DI,完成一些關鍵服務的操作.

關於模組載入的生命週期方法有哪些,如下

 每個介面對應一個生命週期,這和老版Abp的設計也完全不同.優缺點暫時沒發現.

接著,如下:

 呼叫ModuleLoader單例例項,執行載入模組的方法.核心演算法和老版Abp一樣,這裡稍微解釋下,

 核心點如下:

(1)、載入啟動模組所有依賴的模組,並設定依賴項,最後生成IAbpModuleDescriptor集合

 (2)、模組進行拓撲排序,進行迴圈依賴檢測

 ok,下面開始解析核心點原始碼

通過DependsOnAttribute特性來處理模組間的依賴關係.核心程式碼如下:

 拿到當前型別的DependsOnAttribute特性,解析其內部的型別,加入到dependencies依賴型別集合.所以表示模組間的依賴關係根據如上程式碼可以得出兩種模式,如下:

 常用的是第二種.

通過上面的方法拿到所有的依賴型別集合之後,執行下面的遞迴方法

 這樣就可以遍歷出所有的啟動模組以來的所有模組.同時去除了重複的模組.最後遍歷所有的模組生成如下型別的例項

 模組例項的生命週期為單例,如下圖:

接著開始處理啟動引數中配置的外掛模組

 

 外掛模組的三種新增方式如下

public static class PlugInSourceListExtensions
    {
        public static void AddFolder(
            [NotNull] this PlugInSourceList list, 
            [NotNull] string folder, 
            SearchOption searchOption = SearchOption.TopDirectoryOnly)
        {
            Check.NotNull(list, nameof(list));

            list.Add(new FolderPlugInSource(folder, searchOption));
        }

        public static void AddTypes(
            [NotNull] this PlugInSourceList list, 
            params Type[] moduleTypes)
        {
            Check.NotNull(list, nameof(list));

            list.Add(new TypePlugInSource(moduleTypes));
        }

        public static void AddFiles(
            [NotNull] this PlugInSourceList list,
            params string[] filePaths)
        {
            Check.NotNull(list, nameof(list));

            list.Add(new FilePlugInSource(filePaths));
        }
    }

這邊只介紹一種,其餘核心流程都一樣,如下:

FolderPlugInSource新增外掛型別,其核心引數如下:

直接給資料夾路徑+名稱,掃描下面的外掛程式集,並進行程式集過濾,核心的過濾方法如下:

 最後,返回實現了AbpModule的核心模組型別

 ok,接著回到模組載入系統的載入外掛方法,如下:

 

 ok,這裡可以發現亮點

1、你可以同時新增多種形式的外掛宿主,可以是資料夾下所有的外掛程式集、可以是程式集解決方案、也可以是一個指定的程式集檔案.Abp暫時提供了這三種,當然如果你有實力,也可以編寫遠端呼叫程式集外掛.

2、和模組載入系統完成了整合,和上面的流程一樣,加載出所有啟動模組依賴的型別,並寫入DI

 ok,到這裡外掛模組介紹完畢.最後和普通模組一樣生成IAbpModuleDescriptor集合

接著,拿到所有的模組集合之後(包括外掛),開始設定所有模組間的依賴關係,如下,細心的會發現上面的

 

 中有依賴集合.下面的程式碼就是整理這個關係的.

 

 這裡,邏輯很簡單,就不介紹了,直接跳過,主要是通過DependsOnAttribute特性來實現.

接下去介紹核心點二模組進行拓撲排序,進行迴圈依賴檢測

此時,我們拿到了一個完整的模組集合,內部的依賴關係也已經初步執行好.

 

 核心程式碼如下,關於拓撲排序(演算法的核心邏輯自行查閱程式碼,主要內容是按照依賴關係依次加入到集合,後期可一次執行,這樣就可以整合生命週期),防止迴圈依賴就不說了,接著,將啟動模組放到最後為了配合模組生命週期方法的執行.

ok,到這裡兩個核心點介紹完畢.

接下去.如下程式碼

 

 生成如下上下文,並單例寫入DI

這個Item屬性醉了,個人感覺沒什麼用,因為下面這個for迴圈

 接著執行如下程式碼

 所以這兩個生命週期介面執行的時間節點一定要記住.同時上下文會給你DI容器,方便你進行任何必須的型別操作.

接著

 將當前模組型別對應的程式集中所有的型別寫入DI,預設的注入規則上面已經介紹,預設的註冊器型別為DefaultConventionalRegistrar.同時執行生命週期介面IZcfModule.

到這裡已經執行的三個模組生命週期介面如下:

 切記其執行的節點.

 接著開始初始化模組系統,注意,這邊我跳過了DI容器切換的的內容(關於DI容器切換的原始碼分析後續的博文會介紹),程式碼如下:

 

 

 從DI中釋出單例ModuleManager類,執行如下初始化方法

 

 

 這段程式碼進行簡單的模組載入日誌記錄,後面的核心程式碼上面說過,執行預定義的模組生命週期方法,對應如下介面:

 

 

執行這四個介面必須實現的方法,當然在AbpModule中都以virtual標記,所以你可以按照順序一次進行一些型別操作.但是這幾個生命週期函式,上下文只提供ServiceProvider,

功能做了限制.其餘三個生命週期介面提供的是IServiceCollection例項,所以他們之間還是有差別的,除了執行順序之外.

 

ok,到這裡abp vnext2.0的核心模組記載系統核心流程原始碼分析結束了,純屬個人理解,能力有限,有問題請指正!

下一篇會介紹vnext如何完成整個DI切換,換成autofac或者其他容器.以及如何和模組載入系統結合.

&n