0.簡介
整個 Abp 框架由各個模塊組成,基本上可以看做一個程序集一個模塊,不排除一個程序集有多個模塊的可能性。可以看看他官方的這些擴展庫:
可以看到每個項目文件下面都會有一個 xxxModule
的文件,這里就是存放的模塊文件,一個模塊擁有四個生命周期,分別為 PreInitialize()
(預加載)、Initialize()
(初始化)、PostInitialize
(初始化完成)、Shutdown()
(銷毀),前三個根據我們上一篇文章的代碼可以看到,他是先執行預加載方法,然后執行初始化,最后執行初始化完成方法,銷毀方法則是程序退出的時候執行。
模塊的主要作用就是在 Abp 框架加載的時候程序集執行初始化操作的,比如說 Abp 庫自身的 AbpKernelModule
模塊,里面就是各種注入基礎設施,執行初始化操作。
可以看看其中代碼:
public sealed class AbpKernelModule : AbpModule
{
public override void PreInitialize()
{
// 注冊各種過濾器與基礎組件
IocManager.AddConventionalRegistrar(new BasicConventionalRegistrar());
IocManager.Register<IScopedIocResolver, ScopedIocResolver>(DependencyLifeStyle.Transient);
IocManager.Register(typeof(IAmbientScopeProvider<>), typeof(DataContextAmbientScopeProvider<>), DependencyLifeStyle.Transient);
AddAuditingSelectors();
AddLocalizationSources();
AddSettingProviders();
AddUnitOfWorkFilters();
ConfigureCaches();
AddIgnoredTypes();
AddMethodParameterValidators();
}
public override void Initialize()
{
// 這里是執行替換服務的 Action,Abp 允許用戶在預加載操作替換基礎設施的服務
foreach (var replaceAction in ((AbpStartupConfiguration)Configuration).ServiceReplaceActions.Values)
{
replaceAction();
}
// 安裝領域事件總線的基礎設施
IocManager.IocContainer.Install(new EventBusInstaller(IocManager));
IocManager.Register(typeof(IOnlineClientManager<>), typeof(OnlineClientManager<>), DependencyLifeStyle.Singleton);
IocManager.RegisterAssemblyByConvention(typeof(AbpKernelModule).GetAssembly(),
new ConventionalRegistrationConfig
{
InstallInstallers = false
});
}
public override void PostInitialize()
{
// 權限管理器等初始化才做
RegisterMissingComponents();
IocManager.Resolve<SettingDefinitionManager>().Initialize();
IocManager.Resolve<FeatureManager>().Initialize();
IocManager.Resolve<PermissionManager>().Initialize();
IocManager.Resolve<LocalizationManager>().Initialize();
IocManager.Resolve<NotificationDefinitionManager>().Initialize();
IocManager.Resolve<NavigationManager>().Initialize();
if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
{
var workerManager = IocManager.Resolve<IBackgroundWorkerManager>();
workerManager.Start();
workerManager.Add(IocManager.Resolve<IBackgroundJobManager>());
}
}
public override void Shutdown()
{
// 停止所有后台工作者
if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
{
IocManager.Resolve<IBackgroundWorkerManager>().StopAndWaitToStop();
}
}
}
1.模塊發現與注冊
1.1 發現模塊
1.1.1 搜索所有定義的模塊類型
我們定義好模塊之后,Abp 如何發現我們的模塊呢?
在最外部,我們使用 services.AddAbp<TStartModule>()
的時候,就傳入了啟動模塊類型。
在之前 AbpBootstrapper
的 Initialize()
初始化方法當中通過調用 AbpModuleManager.Initialize(Type startupModule)
方法來初始化,在其內部可以看到:
public virtual void Initialize(Type startupModule)
{
_modules = new AbpModuleCollection(startupModule);
LoadAllModules();
}
這里通過傳入啟動模塊來初始化 AboModuleCollection
類。
internal class AbpModuleCollection : List<AbpModuleInfo>
{
public Type StartupModuleType { get; }
public AbpModuleCollection(Type startupModuleType)
{
StartupModuleType = startupModuleType;
}
// 其他代碼
}
初始化完成之后,繼續調用 LoadAllModules()
方法,這里就開始加載模塊了。
private void LoadAllModules()
{
Logger.Debug("Loading Abp modules...");
List<Type> plugInModuleTypes;
// 發現所有 Abp 模塊
var moduleTypes = FindAllModuleTypes(out plugInModuleTypes).Distinct().ToList();
Logger.Debug("Found " + moduleTypes.Count + " ABP modules in total.");
// 注冊 Abp 模塊
RegisterModules(moduleTypes);
// 創建模塊對應的 AbpModuleInfo 包裝類
CreateModules(moduleTypes, plugInModuleTypes);
// 將核心模塊放在第一位初始化
_modules.EnsureKernelModuleToBeFirst();
// 將啟動模塊放在最后一位進行初始化
_modules.EnsureStartupModuleToBeLast();
// 設置每個 ModuleInfo 的依賴關系
SetDependencies();
Logger.DebugFormat("{0} modules loaded.", _modules.Count);
}
繼續跳轉,來到內部 FindAllModuleTypes()
方法,在這個方法里面我們可以看到他調用了 AbpModule
的一個靜態方法來根據其啟動模塊,之后通過啟動模塊上面的 DependsOnAttribute
特性來遞歸找到它所有的依賴模塊。
private List<Type> FindAllModuleTypes(out List<Type> plugInModuleTypes)
{
plugInModuleTypes = new List<Type>();
var modules = AbpModule.FindDependedModuleTypesRecursivelyIncludingGivenModule(_modules.StartupModuleType);
// 其他代碼
return modules;
}
找到模塊之后,在 RegisterModules()
里面通過 IocManager
的注冊方法,將所有模塊都注入到 Ioc 容器當中,注意這里注冊的所有的 Abp 模塊都是單例對象。
1.1.2 包裝模塊信息
在 LoadAllModules()
方法里面,通過 CreateModules()
方法來包裝好 ModuleInfo 類並且將其放在之前初始化完成的 AbpModuleCollection
對象 _modules
里面。
private void CreateModules(ICollection<Type> moduleTypes, List<Type> plugInModuleTypes)
{
foreach (var moduleType in moduleTypes)
{
// 解析剛才在 RegisterModules 里面注冊的單例模塊對象
var moduleObject = _iocManager.Resolve(moduleType) as AbpModule;
if (moduleObject == null)
{
throw new AbpInitializationException("This type is not an ABP module: " + moduleType.AssemblyQualifiedName);
}
// 為這些模塊對象初始化基礎設施
moduleObject.IocManager = _iocManager;
moduleObject.Configuration = _iocManager.Resolve<IAbpStartupConfiguration>();
// 包裝成為 ModuleInfo
var moduleInfo = new AbpModuleInfo(moduleType, moduleObject, plugInModuleTypes.Contains(moduleType));
_modules.Add(moduleInfo);
if (moduleType == _modules.StartupModuleType)
{
StartupModule = moduleInfo;
}
Logger.DebugFormat("Loaded module: " + moduleType.AssemblyQualifiedName);
}
}
在每個 ModuleInfo
對象內部都存放有該模塊的模塊類型信息,以及他的單例對象實例。
1.1.3 確定基本的模塊加載順序
模塊在進行加載的時候,第一個加載的模塊一定是從核心模塊,最后加載的模塊肯定是啟動模塊。所以,這里的 AbpModuleCollection
提供了兩個方法,一個是 EnsureKernelModuleToBeFirst()
,一個是 EnsureStartupModuleToBeLast()
。這兩個方法的作用第一個就是將 AbpKernelModule
放在第一位,第二個就是將啟動模塊放在集合的末尾。
public static void EnsureKernelModuleToBeFirst(List<AbpModuleInfo> modules)
{
var kernelModuleIndex = modules.FindIndex(m => m.Type == typeof(AbpKernelModule));
if (kernelModuleIndex <= 0)
{
// 如果 AbpKernelModule 位於首位則不移動位置
return;
}
var kernelModule = modules[kernelModuleIndex];
modules.RemoveAt(kernelModuleIndex);
modules.Insert(0, kernelModule);
}
public static void EnsureStartupModuleToBeLast(List<AbpModuleInfo> modules, Type startupModuleType)
{
var startupModuleIndex = modules.FindIndex(m => m.Type == startupModuleType);
if (startupModuleIndex >= modules.Count - 1)
{
// 如果啟動模塊位於尾部則則不移動位置
return;
}
var startupModule = modules[startupModuleIndex];
modules.RemoveAt(startupModuleIndex);
modules.Add(startupModule);
}
1.2 依賴解析
之前這些步驟已經將我們程序所使用到的所有模塊已經加載完成,並且進行了一個基本的排序操作,以確保我們的模塊加載順序沒有大問題。但是僅僅這樣是不夠的, 我們還需要確保我們依賴的模塊比被引用的模塊要先加載,這個時候就需要確定每個模塊的依賴關系,並且根據這個依賴關系再次進行排序。
1.2.1 設置每個模塊的依賴模塊
因為我們之前為每個模塊包裝了一個 ModuleInfo
實例,在 ModuleInfo
內部還有一個屬性,叫做:
/// <summary>
/// All dependent modules of this module.
/// </summary>
public List<AbpModuleInfo> Dependencies { get; }
所以,在 LoadAllModules()
方法里面還調用了一個方法,叫做 SetDependencies()
,這個方法也是很簡單的,遍歷已經加載完成的 _modules
集合,在里面再根據 AbpModule
提供的 FindDependedModuleTypes()
方法來獲取該模塊的所有依賴模塊類型。找到之后,在 AbpModuleInfo
集合里面查找對應的依賴模塊的的 ModuleInfo
信息添加到目標模塊的 Dependencies 集合內部。
private void SetDependencies()
{
foreach (var moduleInfo in _modules)
{
moduleInfo.Dependencies.Clear();
//Set dependencies for defined DependsOnAttribute attribute(s).
foreach (var dependedModuleType in AbpModule.FindDependedModuleTypes(moduleInfo.Type))
{
var dependedModuleInfo = _modules.FirstOrDefault(m => m.Type == dependedModuleType);
if (dependedModuleInfo == null)
{
throw new AbpInitializationException("Could not find a depended module " + dependedModuleType.AssemblyQualifiedName + " for " + moduleInfo.Type.AssemblyQualifiedName);
}
if ((moduleInfo.Dependencies.FirstOrDefault(dm => dm.Type == dependedModuleType) == null))
{
moduleInfo.Dependencies.Add(dependedModuleInfo);
}
}
}
}
1.2.2 確定正確的模塊加載順序
在所有基本信息加載完成之后,Abp 並沒有在 AbpModuleManager
的 Initialize()
里面來進行這個重新排序操作,而是在 StartModules()
方法里面來重新排序。
在 StartModules()
通過 AbpModuleCollection
提供的 GetSortedModuleListByDependency()
方法來根據依賴項重新進行了一次排序。
public List<AbpModuleInfo> GetSortedModuleListByDependency()
{
var sortedModules = this.SortByDependencies(x => x.Dependencies);
EnsureKernelModuleToBeFirst(sortedModules);
EnsureStartupModuleToBeLast(sortedModules, StartupModuleType);
return sortedModules;
}
這里使用的是存放在 \Abp\src\Abp\Collections\Extensions\ListExtensions.cs
的一個擴展方法 List<T> SortByDependencies<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies)
,他是針對 List<T>
集合實現的一種拓撲排序。
排序之后的結果就是按照依賴關系來存放的一個集合,之后通過 List 的 Foreach 方法循環調用其三個生命周期方法即可。
public virtual void StartModules()
{
var sortedModules = _modules.GetSortedModuleListByDependency();
sortedModules.ForEach(module => module.Instance.PreInitialize());
sortedModules.ForEach(module => module.Instance.Initialize());
sortedModules.ForEach(module => module.Instance.PostInitialize());
}
1.2.3 擴展:拓撲排序
/// <summary>
/// Extension methods for <see cref="IList{T}"/>.
/// </summary>
public static class ListExtensions
{
/// <summary>
/// Sort a list by a topological sorting, which consider their dependencies
/// </summary>
/// <typeparam name="T">The type of the members of values.</typeparam>
/// <param name="source">A list of objects to sort</param>
/// <param name="getDependencies">Function to resolve the dependencies</param>
/// <returns></returns>
public static List<T> SortByDependencies<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies)
{
/* See: http://www.codeproject.com/Articles/869059/Topological-sorting-in-Csharp
* http://en.wikipedia.org/wiki/Topological_sorting
*/
var sorted = new List<T>();
var visited = new Dictionary<T, bool>();
foreach (var item in source)
{
SortByDependenciesVisit(item, getDependencies, sorted, visited);
}
return sorted;
}
/// <summary>
///
/// </summary>
/// <typeparam name="T">The type of the members of values.</typeparam>
/// <param name="item">Item to resolve</param>
/// <param name="getDependencies">Function to resolve the dependencies</param>
/// <param name="sorted">List with the sortet items</param>
/// <param name="visited">Dictionary with the visited items</param>
private static void SortByDependenciesVisit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<T> sorted, Dictionary<T, bool> visited)
{
bool inProcess;
var alreadyVisited = visited.TryGetValue(item, out inProcess);
if (alreadyVisited)
{
if (inProcess)
{
throw new ArgumentException("Cyclic dependency found! Item: " + item);
}
}
else
{
visited[item] = true;
var dependencies = getDependencies(item);
if (dependencies != null)
{
foreach (var dependency in dependencies)
{
SortByDependenciesVisit(dependency, getDependencies, sorted, visited);
}
}
visited[item] = false;
sorted.Add(item);
}
}
}
后面專門寫文章講解一下拓撲排序,這里貼上代碼,后面會改為文章鏈接的。
貼上詳解鏈接:
https://www.cnblogs.com/myzony/p/9201768.html
2.結語
本篇文章主要針對模塊系統進行了一個較為詳細地分析,后面將會講解 Abp 依賴注入相關的代碼,如果你覺得對你有用請點個贊,謝謝。