[Abp vNext 源码分析] - 2. 模块系统的变化

一、简要说明

本篇文章主要分析 Abp vNext 当中的模块系统,从类型构造层面上来看,Abp vNext 当中不再只是单纯的通过 AbpModuleManager 来管理其他的模块,它现在则是 IModuleManager 和 IModuleLoader 来协同工作,其他的代码逻辑并无太大变化。

Abp vNext 规定每个模块必须继承自 IAbpModule 接口,这样 vNext 系统在启动的时候才会扫描到相应的模块。与原来 Abp 框架一样,每个模块可以通过 DependsOnAttribute 特性来确定依赖关系,算法还是使用拓扑排序算法,来根据依赖性确定模块的加载顺序。(从最顶层的模块,依次加载,直到启动模块。)

640?wx_fmt=png

以我们的 Demo 项目为例,这里通过拓扑排序之后的依赖关系如上图,这样最开始执行的即 AbpDataModule 模块,然后再是 AbpAuditingModule 以此类推,直到我们的启动模块 DemoAppModule

在 Abp vNext 当中,所有的组件库/第三方库都是以模块的形式呈现的,模块负责管理整个库的生命周期,包括注册组件,配置组件,销毁组件等。

在最开始的 Abp 框架当中,一个模块有 4 个生命周期,它们都是在抽象基类 AbpModule 当中定义的,分别是 预加载初始化初始化完成销毁。前三个生命周期是依次执行的 预加载->初始化->初始化完成,而最后一个销毁动作则是在程序终止的时候,通过 AbpModuleManager 遍历模块,调用其 ShutDown() 方法进行销毁动作。

640?wx_fmt=png

新的 Abp vNext 框架除了原有的四个生命周期以外,还抽象出了 IOnPreApplicationInitializationIOnApplicationInitializationIOnPostApplicationInitializationIOnApplicationShutdown。从名字就可以看出来,新的四个生命周期是基于应用程序级别的,而不是模块级别。

这是什么意思呢?在 Abp vNext 框架当中,模块按照功能用途划分为两种类型的模块。第一种是 框架模块,它是框架的核心模块,比如缓存、EF Core 等基础设施就属于框架模块,其模块的逻辑与处理基本都在传统的三个生命周期进行处理。

在我们的 services.AddApplication() 阶段就已经完成所有初始化,可以给 应用程序模块 提供服务。

第二种则是 应用程序模块,这种模块则是实现了特定的业务/功能,例如身份管理、租户管理等,而新增加的四个生命周期基本是为这种类型的模块服务的。

在代码和结构上来说,两者并没有区别,在这里仅仅是按用途进行了一次分类。单就模块系统来说,其基本的作用就类似于一个配置类,配置某种组件的各种参数和一些默认逻辑。

二、源码分析

2.1 模块系统的基础设施

模块的初始化动作是在 AbpApplicationBase 基类开始的,在该基类当中除了注入模块相关的基础设施以外。还定义了模块的初始化方法,即 LoadModules() 方法,在该方法内部是调用的 IModuleLoader 去执行具体的加载操作。

internal AbpApplicationBase(
[NotNull] Type startupModuleType,
[NotNull] IServiceCollection services,
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction
)
{
Check.NotNull(startupModuleType, nameof(startupModuleType));
Check.NotNull(services, nameof(services));


StartupModuleType = startupModuleType;
Services = services;

services.TryAddObjectAccessor<IServiceProvider>();

var options = new AbpApplicationCreationOptions(services);
optionsAction?.Invoke(options);


services.AddSingleton<IAbpApplication>(this);
services.AddSingleton<IModuleContainer>(this);

services.AddCoreServices();

services.AddCoreAbpServices(this, options);


Modules = LoadModules(services, options);
}

private IReadOnlyList<IAbpModuleDescriptor> LoadModules(IServiceCollection services, AbpApplicationCreationOptions options)
{

return services
.GetSingletonInstance<IModuleLoader>()
.LoadModules(
services,
StartupModuleType,
options.PlugInSources
);
}

2.2 模块的初始化

进入 IModuleLoader 的默认实现 ModuleLoader,在它的 LoadModules() 方法中,基本逻辑如下:

  1. 扫描当前应用程序的所有模块类,并构建模块描述对象。

  2. 基于模块描述对象,使用拓扑排序算法来按照模块的依赖性进行排序。

  3. 排序完成之后,遍历排序完成的模块描述对象,依次执行它们的三个生命周期方法。

public IAbpModuleDescriptor[] LoadModules(
IServiceCollection services,
Type startupModuleType,
PlugInSourceList plugInSources
)
{

Check.NotNull(services, nameof(services));
Check.NotNull(startupModuleType, nameof(startupModuleType));
Check.NotNull(plugInSources, nameof(plugInSources));


var modules = GetDescriptors(services, startupModuleType, plugInSources);


modules = SortByDependency(modules, startupModuleType);


ConfigureServices(modules, services);

return modules.ToArray();
}

在搜索模块类型的时候,是使用的 AbpModuleHelper 工具类提供的 .FindAllModuleTypes() 方法。该方法会将我们的启动模块传入,根据模块上面的 DependsOn() 标签递归构建 模块描述对象 的集合。

private List<IAbpModuleDescriptor> GetDescriptors(
IServiceCollection services,
Type startupModuleType,
PlugInSourceList plugInSources
)
{

var modules = new List<AbpModuleDescriptor>();


FillModules(modules, services, startupModuleType, plugInSources);

SetDependencies(modules);


return modules.Cast<IAbpModuleDescriptor>().ToList();
}

protected virtual void FillModules(
List<AbpModuleDescriptor> modules,
IServiceCollection services,
Type startupModuleType,
PlugInSourceList plugInSources
)
{

foreach (var moduleType in AbpModuleHelper.FindAllModuleTypes(startupModuleType))
{
modules.Add(CreateModuleDescriptor(services, moduleType));
}


}

走进 AbpModuleHelper 静态类,其代码与结构与原有的 Abp 框架类似,首先看下它的 FindAllModuleTypes() 方法,根据启动模块的类型递归查找所有的模块类型,并添加到一个集合当中。

public static List<Type> FindAllModuleTypes(Type startupModuleType)
{
var moduleTypes = new List<Type>();

AddModuleAndDependenciesResursively(moduleTypes, startupModuleType);
return moduleTypes;
}

private static void AddModuleAndDependenciesResursively(List<Type> moduleTypes, Type moduleType)
{

AbpModule.CheckAbpModuleType(moduleType);


if (moduleTypes.Contains(moduleType))
{
return;
}

moduleTypes.Add(moduleType);


foreach (var dependedModuleType in FindDependedModuleTypes(moduleType))
{
AddModuleAndDependenciesResursively(moduleTypes, dependedModuleType);
}
}

public static List<Type> FindDependedModuleTypes(Type moduleType)
{
AbpModule.CheckAbpModuleType(moduleType);

var dependencies = new List<Type>();


var dependencyDescriptors = moduleType
.GetCustomAttributes()
.OfType<IDependedTypesProvider>();


foreach (var descriptor in dependencyDescriptors)
{

foreach (var dependedModuleType in descriptor.GetDependedTypes())
{
dependencies.AddIfNotContains(dependedModuleType);
}
}

return dependencies;
}

以上操作完成之后,我们就能获得一个平级的模块描述对象集合,我们如果要使用拓扑排序来重新针对这个集合进行排序,就需要知道每个模块的依赖项,根据 IAbpModuleDescriptor 的定义,我们可以看到它有一个 Dependencies 集合来存储它的依赖项。

public interface IAbpModuleDescriptor
{

Type Type { get; }


Assembly Assembly { get; }


IAbpModule Instance { get; }


bool IsLoadedAsPlugIn { get; }


IReadOnlyList<IAbpModuleDescriptor> Dependencies { get; }
}

而 SetDependencies(List<AbpModuleDescriptor> modules) 方法就是来设置每个模块的依赖项的,代码逻辑很简单。遍历之前的平级模块描述对象集合,根据当前模块的类型定义,找到其依赖项的类型定义。根据这个类型定义去平级的模块描述对象集合搜索,将搜索到的结果存储到当前的模块描述对象中的 Dependencies 属性当中。

protected virtual void SetDependencies(List<AbpModuleDescriptor> modules)
{

foreach (var module in modules)
{
SetDependencies(modules, module);
}
}

protected virtual void SetDependencies(List<AbpModuleDescriptor> modules, AbpModuleDescriptor module)
{

foreach (var dependedModuleType in AbpModuleHelper.FindDependedModuleTypes(module.Type))
{

var dependedModule = modules.FirstOrDefault(m => m.Type == dependedModuleType);
if (dependedModule == null)
{
throw new AbpException("Could not find a depended module " + dependedModuleType.AssemblyQualifiedName + " for " + module.Type.AssemblyQualifiedName);
}


module.AddDependency(dependedModule);
}
}

最后的拓扑排序就不在赘述,关于拓扑排序的算法,可以在我的 这篇 博文当中找到。

关于模块的最后操作,就是执行模块的三个生命周期方法了,这块代码在 ConfigureServices() 方法当中,没什么特别的的处理,遍历整个模块描述对象集合,依次执行几个方法就完了。

只是在这里的生命周期方法与之前的不一样了,这里会为每个方法传入一个服务上下文对象,主要是可以通过 IServiceCollection 来配置各个模块的参数,而不是原来的 Configuration 属性。

protected virtual void ConfigureServices(List<IAbpModuleDescriptor> modules, IServiceCollection services)
{

var context = new ServiceConfigurationContext(services);
services.AddSingleton(context);

foreach (var module in modules)
{
if (module.Instance is AbpModule abpModule)
{
abpModule.ServiceConfigurationContext = context;
}
}


foreach (var module in modules.Where(m => m.Instance is IPreConfigureServices))
{
((IPreConfigureServices)module.Instance).PreConfigureServices(context);
}


foreach (var module in modules)
{
if (module.Instance is AbpModule abpModule)
{
if (!abpModule.SkipAutoServiceRegistration)
{
services.AddAssembly(module.Type.Assembly);
}
}

module.Instance.ConfigureServices(context);
}


foreach (var module in modules.Where(m => m.Instance is IPostConfigureServices))
{
((IPostConfigureServices)module.Instance).PostConfigureServices(context);
}


foreach (var module in modules)
{
if (module.Instance is AbpModule abpModule)
{
abpModule.ServiceConfigurationContext = null;
}
}
}

以上动作都是在 Startup 类当中的 ConfigureService() 方法中执行,你可能会奇怪,剩下的四个应用程序生命周期的方法在哪儿执行的呢?

这几个方法是被抽象成了 IModuleLifecycleContributor 类型,在前面的 AddCoreAbpService()方法的内部就被添加到了配置项里面。

internal static void AddCoreAbpServices(this IServiceCollection services,
IAbpApplication abpApplication,
AbpApplicationCreationOptions applicationCreationOptions
)
{


services.Configure<ModuleLifecycleOptions>(options =>
{
options.Contributors.Add<OnPreApplicationInitializationModuleLifecycleContributor>();
options.Contributors.Add<OnApplicationInitializationModuleLifecycleContributor>();
options.Contributors.Add<OnPostApplicationInitializationModuleLifecycleContributor>();
options.Contributors.Add<OnApplicationShutdownModuleLifecycleContributor>();
});
}

执行的话,则是在 Startup 类的 Configure() 方法当中,它会调用 AbpApplicationBase 基类的 InitializeModules() 方法,在该方法内部也是遍历所有的 Contributor (生命周期),再将所有的模块对应的方法调用一次而已。

public void InitializeModules(ApplicationInitializationContext context)
{
LogListOfModules();


foreach (var Contributor in _lifecycleContributors)
{

foreach (var module in _moduleContainer.Modules)
{
Contributor.Initialize(context, module.Instance);
}
}

_logger.LogInformation("Initialized all modules.");
}

这里操作可能有点看不懂,不是说调用模块的生命周期方法么,为啥还将实例传递给 Contributor 呢?我们找到一个 Contributor 的定义就知道了。

public class OnApplicationInitializationModuleLifecycleContributor : ModuleLifecycleContributorBase
{
public override void Initialize(ApplicationInitializationContext context, IAbpModule module)
{

(module as IOnApplicationInitialization)?.OnApplicationInitialization(context);
}
}

这里我认为 Abp vNext 把 Contributor 抽象出来可能是为了后面方便扩展吧,如果你也有自己的看法不妨在评论区留言。

三、总结

至此,整个模块系统的解析就结束了,如果看过 Abp 框架源码解析的朋友就可以很明显的感觉到,新框架的模块系统除了生命周期多了几个以外,其他的变化很少,基本没太大的变化。

在 Abp vNext 框架里面,模块系统是整个框架的基石,了解了模块系统以后,对于剩下的设计就很好理解了。

原文地址:https://www.cnblogs.com/myzony/p/10734357.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 
640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316211.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P3301 [SDOI2013]方程

P3301 [SDOI2013]方程 题意&#xff1a; 题解&#xff1a; 插板法介绍 首先要先讲组合数学的一个方法&#xff1a;插板法 问题引出&#xff1a;把10个球放进三个盒子&#xff0c;每个箱子至少一个有多少种分法&#xff1f; 10个球就有9个空隙&#xff0c;我们可以考虑在这个…

.NET Framework 4.8发布

原文地址&#xff1a;https://devblogs.microsoft.com/dotnet/announcing-the-net-framework-4-8/我们很高兴地宣布今天发布.NET Framework 4.8。它包含在Windows 10 May 2019更新中。.NET Framework 4.8也可在Windows 7和Windows Server 2008 R2 上使用。您可以从我们的 .NET下…

[NewLife.XCode]数据层缓存(网站性能翻10倍)

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;支持nfx/netcore&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中&#xff0c;代…

[NewLife.XCode]高级查询(化繁为简、分页提升性能)

NewLife.XCode是一个有10多年历史的开源数据中间件&#xff0c;支持nfx/netcore&#xff0c;由新生命团队(2002~2019)开发完成并维护至今&#xff0c;以下简称XCode。整个系列教程会大量结合示例代码和运行日志来进行深入分析&#xff0c;蕴含多年开发经验于其中&#xff0c;代…

微软云Azure训练营 | 八城联动,全球盛会

Global Azure Bootcamp是由微软发起、MVP参与组织的全球化学习交流活动。每年会挑选一个特定的时间&#xff0c;在同一天内&#xff0c;全球不同地区将同时开展。2019年全球Azure训练营&#xff08;Global Azure Bootcamp&#xff09;将于2019年4月27日在全球270多个城市同时举…

长沙4月21日开发者大会暨.NET社区成立大会活动纪实

活动总结2019年4月21日是一个斜风细雨、微风和煦的美好日子&#xff0c;由长沙.NET技术社区、腾讯云云加社区、微软Azure云技术社区、中国.NET技术社区、长沙柳枝行动、长沙互联网活动基地&#xff08;唐胡子俱乐部&#xff09;等多家单位共同主办的长沙开发者技术大会暨长沙.N…

SQL Server AlwaysOn 集群 关于主Server IP与Listener IP调换的详细测试

1. 背景SQL Server 搭建AlwaysOn后&#xff0c;我们就希望程序连接时使用虚拟的侦听IP&#xff08;Listener IP&#xff09;&#xff0c;而不再是主Server 的IP。如果我们有采用中间件&#xff0c;则可以在配置中&#xff0c;直接用Listener IP 替换掉 Server IP&#xff0c;可…

在 DotNetCore 3.0 程序中使用通用协议方式启动文件关联应用

问题描述在传统的基于 .NET Framework 的 WPF 程序中&#xff0c;我们可以使用如下代码段启动相关的默认应用&#xff1a;Copy# 启动默认文本编辑器打开 helloworld.txtProcess.Start("helloworld.txt");# 启动默认浏览器打开 https:Process.Start("https://hip…

.NetCoreLinuxDockerPortainer踩坑历险记

最近有一个云服务器和数据库的迁移任务&#xff0c;踩坑爬坑无数次&#xff0c;觉得必须要记录一下。大家瓜子花生准备好&#xff0c;听我慢慢讲故事#手动笑哭#。故事背景公司是做电商业务的&#xff0c;在天猫有几家旗舰店数据量也很大。阿里有一个称为聚石塔的平台&#xff0…

深入源码理解.NET Core中Startup的注册及运行

开发.NET Core应用&#xff0c;直接映入眼帘的就是Startup类和Program类&#xff0c;它们是.NET Core应用程序的起点。通过使用Startup&#xff0c;可以配置化处理所有向应用程序所做的请求的管道&#xff0c;同时也可以减少.NET应用程序对单一服务器的依赖性&#xff0c;使我们…

Python、Java、TypeScript 和 Perl 作者谈语言设计

Python 作者 Guido van Rossum、Java 作者 James Gosling、Turbo Pascal 和 TypeScript 作者 Anders Hejlsberg&#xff0c;以及 Perl 作者 Larry Wall 本月早些时候齐聚一堂&#xff0c;讨论了(YouTube)编程语言设计的过去和未来。Guido van Rossum 说&#xff0c;设计一种语言…

Asp.Net Core Web应用程序—探索

前言作为一个Windows系统下的开发者&#xff0c;我对于Core的使用机会几乎为0&#xff0c;但是考虑到微软的战略规划&#xff0c;我觉得&#xff0c;Core还是有先了解起来的必要。因为&#xff0c;目前微软已经搞出了两个框架了&#xff0c;一个是Net标准(.NetFramework)&#…

智能优化算法应用:基于跳蛛算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于跳蛛算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于跳蛛算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.跳蛛算法4.实验参数设定5.算法结果6.参考文献7.MA…

让你的ASP.NET Core应用程序更安全

对于ASP.NET Core应用程序&#xff0c;除了提供认证和授权机制来保证服务的安全性&#xff0c;还需要考虑下面的一些安全因素&#xff1a;CSRF强制HTTPS安全的HTTP HeadersCSRFASP.NET Core通过AntiForgeryToken来阻止CSRF攻击&#xff0c;一般来说&#xff0c;当用户做表单提交…

.net core 中间件管道底层剖析

.net core 管道&#xff08;Pipeline&#xff09;是什么&#xff1f;由上图可以看出&#xff0c;.net core 管道是请求抵达服务器到响应结果返回的中间的一系列的处理过程&#xff0c;如果我们简化一下成下图来看的话&#xff0c;.net core 的管道其实就是中间件的部分。微软中…

架构为什么要以领域为核心

很久以前, 人们以为地球是太阳系的中心.然后一位聪明人, 哥白尼, 他改变了我们对太阳系的看法. 他认为太阳是太阳系的中心:这是对太阳更好的一个解释, 更简单也更具说服力. 事实上, 以太阳为中心的模型确实是更优雅的.上面这件事也发生在软件开发里. 下面这个就是很多开发者惯用…

201403-5 任务调度

哇&#xff0c;ccf csp认证考试 历年真题解&#xff08;一本书&#xff09;真厉害。 #include<iostream> #include<cstdio> #include<algorithm> #include<cstring>using namespace std;typedef long long LL; typedef pair<int,int> PII; con…

C# - 为引用类型重定义相等性 - 继承相关

派生类这是上面Citizen类的一个子类&#xff1a;下面我重写object.Equals() 方法&#xff1a;大部分逻辑都在base.Equals()方法里了&#xff0c;首先如果父类的Equals()方法返回false&#xff0c;那么下面也就不用做啥了。但是如果父类Equals()认为这两个实例是相等的&#xff…

微软百名员工签名力挺996.ICU

中国程序员上传到 GitHub 的 996.ICU repo 火速在互联网广泛传播时&#xff0c;996 工作制引起了全球的广泛关注&#xff0c;Python 之父直指这是不人道的行为&#xff0c;事情经过不断发酵&#xff0c;中国官方媒体也接连发声表态要警惕「996 工作制」。就在今日&#xff0c;微…

P1174 打砖块

P1174 打砖块 题意&#xff1a; 题解&#xff1a; 参考题解&#xff1a; I_AM_HelloWord danxmz2006 这两个博客结合看&#xff0c;大致就能理解 我们只在N处转移&#xff0c;面对Y类的块无需决策&#xff0c;因为Y类的块可以一直打 不同的打砖块的顺序&#xff0c;决定了我…