[Abp 源碼分析]二、模塊系統


0.簡介

整個 Abp 框架由各個模塊組成,基本上可以看做一個程序集一個模塊,不排除一個程序集有多個模塊的可能性。可以看看他官方的這些擴展庫:

img

可以看到每個項目文件下面都會有一個 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>() 的時候,就傳入了啟動模塊類型。

在之前 AbpBootstrapperInitialize() 初始化方法當中通過調用 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;
}

img

找到模塊之后,在 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);
			}
		}
	}
}

img

1.2.2 確定正確的模塊加載順序

在所有基本信息加載完成之后,Abp 並沒有在 AbpModuleManagerInitialize() 里面來進行這個重新排序操作,而是在 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 依賴注入相關的代碼,如果你覺得對你有用請點個贊,謝謝。

3.點此跳轉到總目錄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM