0.简介

整个 Abp 框架由各个模块组成,基本上可以看做一个程序集一个模块,不排除一个程序集有多个模块的可能性。可以看看他官方的这些扩展库:

可以看到每个项目文件下面都会有一个 xxxModule 的文件,这里就是存放的模块文件,一个模块拥有四个生命周期,分别为 PreInitialize()(预加载)、Initialize()(初始化)、PostInitialize(初始化完成)、Shutdown()(销毁),前三个根据我们上一篇文章的代码可以看到,他是先执行预加载方法,然后执行初始化,最后执行初始化完成方法,销毁方法则是程序退出的时候执行。

模块的主要作用就是在 Abp 框架加载的时候程序集执行初始化操作的,比如说 Abp 库自身的 AbpKernelModule 模块,里面就是各种注入基础设施,执行初始化操作。

可以看看其中代码:

  1. public sealed class AbpKernelModule : AbpModule
  2. {
  3. public override void PreInitialize()
  4. {
  5. // 注册各种过滤器与基础组件
  6. IocManager.AddConventionalRegistrar(new BasicConventionalRegistrar());
  7. IocManager.Register<IScopedIocResolver, ScopedIocResolver>(DependencyLifeStyle.Transient);
  8. IocManager.Register(typeof(IAmbientScopeProvider<>), typeof(DataContextAmbientScopeProvider<>), DependencyLifeStyle.Transient);
  9. AddAuditingSelectors();
  10. AddLocalizationSources();
  11. AddSettingProviders();
  12. AddUnitOfWorkFilters();
  13. ConfigureCaches();
  14. AddIgnoredTypes();
  15. AddMethodParameterValidators();
  16. }
  17. public override void Initialize()
  18. {
  19. // 这里是执行替换服务的 Action,Abp 允许用户在预加载操作替换基础设施的服务
  20. foreach (var replaceAction in ((AbpStartupConfiguration)Configuration).ServiceReplaceActions.Values)
  21. {
  22. replaceAction();
  23. }
  24. // 安装领域事件总线的基础设施
  25. IocManager.IocContainer.Install(new EventBusInstaller(IocManager));
  26. IocManager.Register(typeof(IOnlineClientManager<>), typeof(OnlineClientManager<>), DependencyLifeStyle.Singleton);
  27. IocManager.RegisterAssemblyByConvention(typeof(AbpKernelModule).GetAssembly(),
  28. new ConventionalRegistrationConfig
  29. {
  30. InstallInstallers = false
  31. });
  32. }
  33. public override void PostInitialize()
  34. {
  35. // 权限管理器等初始化才做
  36. RegisterMissingComponents();
  37. IocManager.Resolve<SettingDefinitionManager>().Initialize();
  38. IocManager.Resolve<FeatureManager>().Initialize();
  39. IocManager.Resolve<PermissionManager>().Initialize();
  40. IocManager.Resolve<LocalizationManager>().Initialize();
  41. IocManager.Resolve<NotificationDefinitionManager>().Initialize();
  42. IocManager.Resolve<NavigationManager>().Initialize();
  43. if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
  44. {
  45. var workerManager = IocManager.Resolve<IBackgroundWorkerManager>();
  46. workerManager.Start();
  47. workerManager.Add(IocManager.Resolve<IBackgroundJobManager>());
  48. }
  49. }
  50. public override void Shutdown()
  51. {
  52. // 停止所有后台工作者
  53. if (Configuration.BackgroundJobs.IsJobExecutionEnabled)
  54. {
  55. IocManager.Resolve<IBackgroundWorkerManager>().StopAndWaitToStop();
  56. }
  57. }
  58. }

1.模块发现与注册

1.1 发现模块

1.1.1 搜索所有定义的模块类型

我们定义好模块之后,Abp 如何发现我们的模块呢?

在最外部,我们使用 services.AddAbp<TStartModule>() 的时候,就传入了启动模块类型。

在之前 AbpBootstrapperInitialize() 初始化方法当中通过调用 AbpModuleManager.Initialize(Type startupModule) 方法来初始化,在其内部可以看到:

  1. public virtual void Initialize(Type startupModule)
  2. {
  3. _modules = new AbpModuleCollection(startupModule);
  4. LoadAllModules();
  5. }

这里通过传入启动模块来初始化 AboModuleCollection 类。

  1. internal class AbpModuleCollection : List<AbpModuleInfo>
  2. {
  3. public Type StartupModuleType { get; }
  4. public AbpModuleCollection(Type startupModuleType)
  5. {
  6. StartupModuleType = startupModuleType;
  7. }
  8. // 其他代码
  9. }

初始化完成之后,继续调用 LoadAllModules() 方法,这里就开始加载模块了。

  1. private void LoadAllModules()
  2. {
  3. Logger.Debug("Loading Abp modules...");
  4. List<Type> plugInModuleTypes;
  5. // 发现所有 Abp 模块
  6. var moduleTypes = FindAllModuleTypes(out plugInModuleTypes).Distinct().ToList();
  7. Logger.Debug("Found " + moduleTypes.Count + " ABP modules in total.");
  8. // 注册 Abp 模块
  9. RegisterModules(moduleTypes);
  10. // 创建模块对应的 AbpModuleInfo 包装类
  11. CreateModules(moduleTypes, plugInModuleTypes);
  12. // 将核心模块放在第一位初始化
  13. _modules.EnsureKernelModuleToBeFirst();
  14. // 将启动模块放在最后一位进行初始化
  15. _modules.EnsureStartupModuleToBeLast();
  16. // 设置每个 ModuleInfo 的依赖关系
  17. SetDependencies();
  18. Logger.DebugFormat("{0} modules loaded.", _modules.Count);
  19. }

继续跳转,来到内部 FindAllModuleTypes() 方法,在这个方法里面我们可以看到他调用了 AbpModule 的一个静态方法来根据其启动模块,之后通过启动模块上面的 DependsOnAttribute 特性来递归找到它所有的依赖模块。

  1. private List<Type> FindAllModuleTypes(out List<Type> plugInModuleTypes)
  2. {
  3. plugInModuleTypes = new List<Type>();
  4. var modules = AbpModule.FindDependedModuleTypesRecursivelyIncludingGivenModule(_modules.StartupModuleType);
  5. // 其他代码
  6. return modules;
  7. }

找到模块之后,在 RegisterModules() 里面通过 IocManager 的注册方法,将所有模块都注入到 Ioc 容器当中,注意这里注册的所有的 Abp 模块都是单例对象。

1.1.2 包装模块信息

LoadAllModules() 方法里面,通过 CreateModules() 方法来包装好 ModuleInfo 类并且将其放在之前初始化完成的 AbpModuleCollection 对象 _modules 里面。

  1. private void CreateModules(ICollection<Type> moduleTypes, List<Type> plugInModuleTypes)
  2. {
  3. foreach (var moduleType in moduleTypes)
  4. {
  5. // 解析刚才在 RegisterModules 里面注册的单例模块对象
  6. var moduleObject = _iocManager.Resolve(moduleType) as AbpModule;
  7. if (moduleObject == null)
  8. {
  9. throw new AbpInitializationException("This type is not an ABP module: " + moduleType.AssemblyQualifiedName);
  10. }
  11. // 为这些模块对象初始化基础设施
  12. moduleObject.IocManager = _iocManager;
  13. moduleObject.Configuration = _iocManager.Resolve<IAbpStartupConfiguration>();
  14. // 包装成为 ModuleInfo
  15. var moduleInfo = new AbpModuleInfo(moduleType, moduleObject, plugInModuleTypes.Contains(moduleType));
  16. _modules.Add(moduleInfo);
  17. if (moduleType == _modules.StartupModuleType)
  18. {
  19. StartupModule = moduleInfo;
  20. }
  21. Logger.DebugFormat("Loaded module: " + moduleType.AssemblyQualifiedName);
  22. }
  23. }

在每个 ModuleInfo 对象内部都存放有该模块的模块类型信息,以及他的单例对象实例。

1.1.3 确定基本的模块加载顺序

模块在进行加载的时候,第一个加载的模块一定是从核心模块,最后加载的模块肯定是启动模块。所以,这里的 AbpModuleCollection 提供了两个方法,一个是 EnsureKernelModuleToBeFirst() ,一个是 EnsureStartupModuleToBeLast() 。这两个方法的作用第一个就是将 AbpKernelModule 放在第一位,第二个就是将启动模块放在集合的末尾。

  1. public static void EnsureKernelModuleToBeFirst(List<AbpModuleInfo> modules)
  2. {
  3. var kernelModuleIndex = modules.FindIndex(m => m.Type == typeof(AbpKernelModule));
  4. if (kernelModuleIndex <= 0)
  5. {
  6. // 如果 AbpKernelModule 位于首位则不移动位置
  7. return;
  8. }
  9. var kernelModule = modules[kernelModuleIndex];
  10. modules.RemoveAt(kernelModuleIndex);
  11. modules.Insert(0, kernelModule);
  12. }
  1. public static void EnsureStartupModuleToBeLast(List<AbpModuleInfo> modules, Type startupModuleType)
  2. {
  3. var startupModuleIndex = modules.FindIndex(m => m.Type == startupModuleType);
  4. if (startupModuleIndex >= modules.Count - 1)
  5. {
  6. // 如果启动模块位于尾部则则不移动位置
  7. return;
  8. }
  9. var startupModule = modules[startupModuleIndex];
  10. modules.RemoveAt(startupModuleIndex);
  11. modules.Add(startupModule);
  12. }

1.2 依赖解析

之前这些步骤已经将我们程序所使用到的所有模块已经加载完成,并且进行了一个基本的排序操作,以确保我们的模块加载顺序没有大问题。但是仅仅这样是不够的, 我们还需要确保我们依赖的模块比被引用的模块要先加载,这个时候就需要确定每个模块的依赖关系,并且根据这个依赖关系再次进行排序。

1.2.1 设置每个模块的依赖模块

因为我们之前为每个模块包装了一个 ModuleInfo实例,在 ModuleInfo 内部还有一个属性,叫做:

  1. /// <summary>
  2. /// All dependent modules of this module.
  3. /// </summary>
  4. public List<AbpModuleInfo> Dependencies { get; }

所以,在 LoadAllModules() 方法里面还调用了一个方法,叫做 SetDependencies(),这个方法也是很简单的,遍历已经加载完成的 _modules 集合,在里面再根据 AbpModule 提供的 FindDependedModuleTypes() 方法来获取该模块的所有依赖模块类型。找到之后,在 AbpModuleInfo 集合里面查找对应的依赖模块的的 ModuleInfo 信息添加到目标模块的 Dependencies 集合内部。

  1. private void SetDependencies()
  2. {
  3. foreach (var moduleInfo in _modules)
  4. {
  5. moduleInfo.Dependencies.Clear();
  6. //Set dependencies for defined DependsOnAttribute attribute(s).
  7. foreach (var dependedModuleType in AbpModule.FindDependedModuleTypes(moduleInfo.Type))
  8. {
  9. var dependedModuleInfo = _modules.FirstOrDefault(m => m.Type == dependedModuleType);
  10. if (dependedModuleInfo == null)
  11. {
  12. throw new AbpInitializationException("Could not find a depended module " + dependedModuleType.AssemblyQualifiedName + " for " + moduleInfo.Type.AssemblyQualifiedName);
  13. }
  14. if ((moduleInfo.Dependencies.FirstOrDefault(dm => dm.Type == dependedModuleType) == null))
  15. {
  16. moduleInfo.Dependencies.Add(dependedModuleInfo);
  17. }
  18. }
  19. }
  20. }

1.2.2 确定正确的模块加载顺序

在所有基本信息加载完成之后,Abp 并没有在 AbpModuleManagerInitialize() 里面来进行这个重新排序操作,而是在 StartModules() 方法里面来重新排序。

StartModules() 通过 AbpModuleCollection 提供的 GetSortedModuleListByDependency() 方法来根据依赖项重新进行了一次排序。

  1. public List<AbpModuleInfo> GetSortedModuleListByDependency()
  2. {
  3. var sortedModules = this.SortByDependencies(x => x.Dependencies);
  4. EnsureKernelModuleToBeFirst(sortedModules);
  5. EnsureStartupModuleToBeLast(sortedModules, StartupModuleType);
  6. return sortedModules;
  7. }

这里使用的是存放在 \Abp\src\Abp\Collections\Extensions\ListExtensions.cs 的一个扩展方法 List<T> SortByDependencies<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies),他是针对 List<T> 集合实现的一种拓扑排序。

排序之后的结果就是按照依赖关系来存放的一个集合,之后通过 List 的 Foreach 方法循环调用其三个生命周期方法即可。

  1. public virtual void StartModules()
  2. {
  3. var sortedModules = _modules.GetSortedModuleListByDependency();
  4. sortedModules.ForEach(module => module.Instance.PreInitialize());
  5. sortedModules.ForEach(module => module.Instance.Initialize());
  6. sortedModules.ForEach(module => module.Instance.PostInitialize());
  7. }

1.2.3 扩展:拓扑排序

  1. /// <summary>
  2. /// Extension methods for <see cref="IList{T}"/>.
  3. /// </summary>
  4. public static class ListExtensions
  5. {
  6. /// <summary>
  7. /// Sort a list by a topological sorting, which consider their dependencies
  8. /// </summary>
  9. /// <typeparam name="T">The type of the members of values.</typeparam>
  10. /// <param name="source">A list of objects to sort</param>
  11. /// <param name="getDependencies">Function to resolve the dependencies</param>
  12. /// <returns></returns>
  13. public static List<T> SortByDependencies<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies)
  14. {
  15. /* See: http://www.codeproject.com/Articles/869059/Topological-sorting-in-Csharp
  16. * http://en.wikipedia.org/wiki/Topological_sorting
  17. */
  18. var sorted = new List<T>();
  19. var visited = new Dictionary<T, bool>();
  20. foreach (var item in source)
  21. {
  22. SortByDependenciesVisit(item, getDependencies, sorted, visited);
  23. }
  24. return sorted;
  25. }
  26. /// <summary>
  27. ///
  28. /// </summary>
  29. /// <typeparam name="T">The type of the members of values.</typeparam>
  30. /// <param name="item">Item to resolve</param>
  31. /// <param name="getDependencies">Function to resolve the dependencies</param>
  32. /// <param name="sorted">List with the sortet items</param>
  33. /// <param name="visited">Dictionary with the visited items</param>
  34. private static void SortByDependenciesVisit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<T> sorted, Dictionary<T, bool> visited)
  35. {
  36. bool inProcess;
  37. var alreadyVisited = visited.TryGetValue(item, out inProcess);
  38. if (alreadyVisited)
  39. {
  40. if (inProcess)
  41. {
  42. throw new ArgumentException("Cyclic dependency found! Item: " + item);
  43. }
  44. }
  45. else
  46. {
  47. visited[item] = true;
  48. var dependencies = getDependencies(item);
  49. if (dependencies != null)
  50. {
  51. foreach (var dependency in dependencies)
  52. {
  53. SortByDependenciesVisit(dependency, getDependencies, sorted, visited);
  54. }
  55. }
  56. visited[item] = false;
  57. sorted.Add(item);
  58. }
  59. }
  60. }

后面专门写文章讲解一下拓扑排序,这里贴上代码,后面会改为文章链接的。

贴上详解链接:

https://www.cnblogs.com/myzony/p/9201768.html

2.结语

本篇文章主要针对模块系统进行了一个较为详细地分析,后面将会讲解 Abp 依赖注入相关的代码,如果你觉得对你有用请点个赞,谢谢。

3.点此跳转到总目录

[Abp 源码分析]二、模块系统的更多相关文章

  1. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  2. ABP源码分析二十二:Navigation

    MenuDefinition:封装了导航栏上的主菜单的属性. MenuItemDefinition:封装了主菜单的子菜单的属性.子菜单可以引用其他子菜单构成一个菜单树 UserMenu/UserMen ...

  3. ABP源码分析二十三:Authorization

    Permission:用于定义一个Permission,一个permission可以包含多个子Permission. PermissionDictionary:继承自Dictionary<str ...

  4. ABP源码分析二十:ApplicationService

    IApplicationService : 空接口,起标识作用.所有实现了IApplicationService 的类都会被自动注入到容器中.同时所有IApplicationService对象都会被注 ...

  5. ABP源码分析二十一:Feature

    Feature是什么?Feature就是对function的分类方法,其与function的关系就比如Role和User的关系一样. ABP中Feature具有以下属性: 其中最重要的属性是name, ...

  6. ABP源码分析二十四:Notification

    NotificationDefinition: 用于封装Notification Definnition 的信息.注意和Notification 的区别,如果把Notification看成是具体的消息 ...

  7. ABP源码分析二十五:EventBus

    IEventData/EventData: 封装了EventData信息,触发event的源对象和时间 IEventBus/EventBus: 定义和实现了了一系列注册,注销和触发事件处理函数的方法. ...

  8. ABP源码分析二十六:核心框架中的一些其他功能

    本文是ABP核心项目源码分析的最后一篇,介绍一些前面遗漏的功能 AbpSession AbpSession: 目前这个和CLR的Session没有什么直接的联系.当然可以自定义的去实现IAbpSess ...

  9. ABP源码分析二十八:ABP.MemoryDB

    这个模块简单,且无实际作用.一般实际项目中都有用数据库做持久化,用了数据库就无法用这个MemoryDB 模块了.原因在于ABP限制了UnitOfWork的类型只能有一个(前文以作介绍),一般用了数据库 ...

  10. ABP源码分析二十九:ABP.MongoDb

    这个Module通过建立一个MongoDbRepositoryBase<TEntity> 基类,封装了对MongoDb数据库的操作. 这个module通过引用MongoDB.Driver, ...

随机推荐

  1. Mongodb4.0副本集构建

    目前最新的mongodb4.0.2已经支持事务这个重要特性,需要使用的话必须是复制或副本集,这是第一篇先研发如何构建副本集,因为副本集是目前最低成本的高可用群集方式. 1.准备三台服务器,本次使用是的 ...

  2. net core体系-Standard-1概述

    前言 早上起来.NET社区沸腾了,期待已久的.NET Core 2.0终于发布!根据个人经验,微软的产品一般在2.0时会趋于成熟,所以一个新的.Net开发时代已经来临!未来属于.NET Core. . ...

  3. 关于jQuery的append方法不能多次添加同一个DOM元素的解决方法

    资料来自:https://segmentfault.com/q/1010000007677851?_ea=1419689 append()方法在jQuery中是使用appendChild()实现的,实 ...

  4. Tomcat配置https后,并发较大时,频繁超时情况。

    tomcat配置ssl后,出现频繁的访问超时情况. 通过脚本(感谢UCloud的技术支持 金晓帆-): netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a i ...

  5. Mac下brew安装JDK的教程

    ---恢复内容开始--- 安装命令: brew cask install java 默认应该会下载jdk7 也可以指定下载版本brew cask install java6 注意: brew inst ...

  6. CheckedTextView文字不居中的问题

    问题:CheckedTextView设置了android:gravity="center",但是不居中 解决方法:添加属性android:textAlignment="c ...

  7. StackExchange.Redis超时的问题

    最近公司有个项目,在请求量大的情况下,有大量的错误日志是关于redis超时的问题: Timeout performing SET XXX, inst: 27, mgr: ProcessReadQueu ...

  8. thrift小试--C++

    [转自]http://blog.csdn.net/poechant/article/details/6618284# Thrift可以实现C++.Java.Python等多种语言的自动生成,此处以C+ ...

  9. 字符串转义为HTML

    有时候后台返回的数据中有字符串,并需要将字符串转化为HTML,下面封装了一个方法,如下 // html转义 function htmlspecialchars_decode(string, quote ...

  10. jieba中文分词

      jieba中文分词¶   中文与拉丁语言不同,不是以空格分开每个有意义的词,在我们处理自然语言处理的时候,大部分情况下,词汇是对句子和文章的理解基础.因此需要一个工具去把完整的中文分解成词. ji ...