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或者其他容器.以及如何和模塊加載系統結合.