[Abp 源码分析]二、模块系统
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 依赖注入相关的代码,如果你觉得对你有用请点个赞,谢谢。
3.点此跳转到总目录
[Abp 源码分析]二、模块系统的更多相关文章
- ABP源码分析二:ABP中配置的注册和初始化
一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...
- ABP源码分析二十二:Navigation
MenuDefinition:封装了导航栏上的主菜单的属性. MenuItemDefinition:封装了主菜单的子菜单的属性.子菜单可以引用其他子菜单构成一个菜单树 UserMenu/UserMen ...
- ABP源码分析二十三:Authorization
Permission:用于定义一个Permission,一个permission可以包含多个子Permission. PermissionDictionary:继承自Dictionary<str ...
- ABP源码分析二十:ApplicationService
IApplicationService : 空接口,起标识作用.所有实现了IApplicationService 的类都会被自动注入到容器中.同时所有IApplicationService对象都会被注 ...
- ABP源码分析二十一:Feature
Feature是什么?Feature就是对function的分类方法,其与function的关系就比如Role和User的关系一样. ABP中Feature具有以下属性: 其中最重要的属性是name, ...
- ABP源码分析二十四:Notification
NotificationDefinition: 用于封装Notification Definnition 的信息.注意和Notification 的区别,如果把Notification看成是具体的消息 ...
- ABP源码分析二十五:EventBus
IEventData/EventData: 封装了EventData信息,触发event的源对象和时间 IEventBus/EventBus: 定义和实现了了一系列注册,注销和触发事件处理函数的方法. ...
- ABP源码分析二十六:核心框架中的一些其他功能
本文是ABP核心项目源码分析的最后一篇,介绍一些前面遗漏的功能 AbpSession AbpSession: 目前这个和CLR的Session没有什么直接的联系.当然可以自定义的去实现IAbpSess ...
- ABP源码分析二十八:ABP.MemoryDB
这个模块简单,且无实际作用.一般实际项目中都有用数据库做持久化,用了数据库就无法用这个MemoryDB 模块了.原因在于ABP限制了UnitOfWork的类型只能有一个(前文以作介绍),一般用了数据库 ...
- ABP源码分析二十九:ABP.MongoDb
这个Module通过建立一个MongoDbRepositoryBase<TEntity> 基类,封装了对MongoDb数据库的操作. 这个module通过引用MongoDB.Driver, ...
随机推荐
- MBR分区操作-增加、扩展、删除
MBR分区操作-增加.扩展.删除 GPT分区参考 http://www.blogjava.net/haha1903/archive/2011/12/21/366942.html l fdisk 显示 ...
- net core EF 链接mysql 数据库
这个主要是一个demo.就在一个工程里面写的 安装MySql.Data.EntityFrameworkCore 增加DbContext 相当于程序与数据库的中间层 public class Ident ...
- python中用xlsxwriter创建图表
缺点:xlsxwriter不能对已存在的Excel进行编辑插入图标 生成图标需要: 1.先准备数据 2.将数据插入到excel中 3.根据插入的数据生成图表 这里的生成excel主要分为准备多维数 ...
- qt之数据库对照片的存取
需要确保数据库连接上 QOCI为驱动 //oracle 数据库连接 //需要在执行文件目录添加 oci.dll oraociei11.dll QSqlDatabase db = QSqlDatabas ...
- 动态规划——Dungeon Game
这又是个题干很搞笑的题目:恶魔把公主囚禁在魔宫的右下角,骑士从魔宫的左上角开始穿越整个魔宫到右下角拯救公主,为了以最快速度拯救公主,骑士每次只能向下或者向右移动一个房间, 每个房间内都有一个整数值,负 ...
- 开始食用grpc(之一)
开始食用grpc(之一) 转载请注明出处:https://www.cnblogs.com/funnyzpc/p/9501353.html ``` 记一次和一锅们压马路,路过一咖啡厅(某巴克),随口 ...
- IntelliJ IDEA最新破解版2018.3.1(附2018.2.2 完美破解教程)
2018.3.1最新版破解 1.官网下载IDEA 2018.3.1的商业版本点我去下载 2.破解jar下载:JetbrainsIdesCrack-3.4-release-enc.jar点我去下载 3. ...
- Java equals()方法和hashCode()方法
equals()方法 如果满足了以下任何一个条件,就不需要覆盖equals()方法: 1 类的每个实例本质上都是唯一的. 2 不关心类是否提供了“逻辑相等”的测试功能. 3 父类已经覆盖了equals ...
- linux查看分区是否开启acl权限
1.为什么需要ACL权限 ACL的全称是 Access Control List (访问控制列表) .对于文件或者目录,都有相应的操作权限 r(read 读),w(write 写),x(execute ...
- DevOps详解
最近我阅读了很多有关DevOps的文章,其中一些非常有趣,然而一些内容也很欠考虑.貌似很多人越来越坚定地在DevOps与chef.puppet或Docker容器的熟练运用方面划了等号.对此我有不同看法 ...