abp vnext2.0之核心组件模块加载系统源码解析

abp vnext是abp官方在abp的基础之上构建的微服务架构,说实话,看完核心组件源码的时候,很兴奋,整个框架将组件化的细想运用的很好,真的超级解耦.老版整个框架依赖Castle的问题,vnext对其进行了解耦,支持AutoFac或者使用.Net Core的默认容器.vnext依然沿用EF core为主,其余ORM为辅助的思想,当然EF core来实现DDD确实有优势,EventBus提供了分布式版本,并提供了RabbitMQ的实现版本,Aop拦截器依然采用Castle.Core.AsyncInterceptor.这一点Dora.Interception貌似可以解决,估计如果高度组件化,那么这也是一个扩展点.整个模块加载系统更加的完善,提供了跟多可选择的特性,工作单元也进行了小幅度的重构,代码更加的通俗易懂(在实现异步工作单元嵌套的设计就有体现)等等还有很多,当然不是本文的重点,vnext2.0是个值得使用的框架.下面开始回到正题.

1、模块加载系统

模块加载系统算是vnext的整个框架的入口,离了他,这个框架就废了.具体它有什么作用,看下面的代码分析,模块加载系统的入口如下:

 每个应用框架必须要有一个启动模块类型,可以通过泛型或者Type实例传入,并且给定启动参数.

启动模块类型:虽然上面给定的约束是必须实现IAbpModule,但是大多数的实现情况是

暂时不讲 AbpModule的源码,后面分析到具体的流程再做介绍.

ok,看看AbpApplicationFactory工厂做了什么了,通过名字分析很明显.AbpApplication的工厂.

分析这个方法就能得出,只要传入启动模块的类型和DI的ServiceCollection和启动应用的参数,就能构建一个IAbpApplicationWithExternalServiceProvider,那么看看IAbpApplicationWithExternalServiceProvider都有什么

 构建完成基本的实体后,调用Initialize方法初始化框架.再看看IAbpApplication接口

 包含启动模块类型,DI注入集合、DI服务提供类,以及一个关闭应用程序必须执行的ShutDown方法.在看看IModuleContainer

 包含模块集合,在Abp中,模块代表一个程序集.这里就是启动abp vnext框架的启动模块类型所依赖的所有模块类型,即所有的程序集集合你可以这样理解.因为一个Module类型(继承AbpModule类型或者实现IAbpModule接口的类型)代表一个程序集.且一个程序集只有一个Module类型(继承AbpModule类型或者实现IAbpModule接口的类型).

ok,接着回到上面的代码

 此处省略一些无关核心流程的代码,代码如下:

 简单的一些非空校验,这里有一个非常有趣的设计,如下:

继续查看,如下

 

 ObjectAccessor源码如下:

 类似装饰者模式,内部容纳一个类型.最后

 ok,到这里整个流程大致就是,给IServiceProvider创建一个ObjectAccessor,且ObjectAccessor没有Value值,同时将ObjectAccessor写入DI,并做了简单的搜索优化.关于IServiceProvider的ObjectAccessor的作用,暂时不介绍,后续会说.

接着看如下代码:

 初始化外部设置参数,接招向DI中注入IAbpApplication和IModuleContainer的单例对象.

接着看下面的代码:

 

注入配置文件、日志、国际化等服务.接着看AddCoeAbpServices方法

 

注入ModuleLoader(处理程序集间依赖关系,处理模块加载生命周期、的核心类型)、程序集发现类(所有程序集都能通过该类型拿到,只要程序集加入到了框架)、类型发现类(程序集集合所包含的所有类型)

 初始化配置文件系统、等等操作,接着看如下代码,将上述类型写入DI

 接下去这行代码就有趣了,如下:

看看它干了什么,如下:

 看看 services.GetConventionalRegistrars干了什么,如下:

 很明显,从DI中读取程序集注册规则类列表,如果没有,则写入默认的程序集注册规则类.所以,这里如果你想自定义程序集注册规则,那么只需在有效的应用程序加载生命周期阶段注入自定义的程序集注册类即可,该类型必须实现下图所示接口

 ok,这个扩展点讲完之后,看看默认的程序集注册规则类DefaultConventionalRegistrar干了什么,如下:

 

 很简单,自行阅读,再看看AddType的实现,如下:

 

 支持类型跳过,如果类型打了DisableConventionalRegistrationAttribute特性,那么该类型将不会被写入DI.

 如果当前类型没有打DependencyAttribute,或者打了DependencyAttribute特性,没有设置Lifetime,则当前类型也不会写入DI.

这里注意,根据代码可以发现,abp给类型生命周期的方式有两种,老版只有一种,如下:

第一种:

 

 

 通过实现ISingletonDependency(单例注入),ITransientDependency(普通引用类型),IScopedDependency(范围内唯一)三大接口来表示当前类型的生命周期,老版abp也是使用这种方式,但是没有IScopedDependency

第二种:

 

 通过DependencyAttribute特性,结构如下

接着,如下代码

 如果当前类型打了ExposeServicesAttribute特性,那么则会调用该特性的如下方法

 

 这个方法的用途是找出如果我们需要从DI中释出个类型,可以使用哪几种方式(常用的是接口,自身等),示例代码如下:

 那么如果需要在框架中使用TestClass的实现,可以用ITestClass接口进行依赖注入,因为

 当然这里可以写多个,因为

 ExposeServicesAttribute特性中的IncludeDefaults和IncludeSelf属性是默认的策略,

IncludeDefaults设置为true是根据类型找出其实现的接口,且接口必须以I字母开头,且接口后面的名字必须和当前类型相等.如果匹配那么该接口有效,也可以进行依赖注入.

IncludeSelf设置为true,则可以通过当前类型进行依赖注入.

接着看如下代码

 

 很简单,只需在有效的应用程序加载生命周期阶段注入指定的Action,注入方式如下:

使用例子,类型映射,如下:

最后看如下代码

这段代码很简单,就不解释了.DependencyAttribute特性给上对应的值就能执行指定的操作,ok,到这里总结一下这种设计的用处,非常nice,原先老版abp注册系统核心单例类型是依赖castle的,如果换成这种设计方式,更加的灵活,如果我们需要给底层添加一个核心类,只需要创建一个类,然后配合Dependency特性和ExposeServices特性即可和DI完美集合,同时还提供了Action扩展,让你可以干很多的事情,就这一点,比老版abp好太多.到这里DefaultConventionalRegistrar介绍完毕

ok,在回到AddCoreAbpServices方法,如下:

 这里也很简单,向DI中预先写入AbpModuleLifecycleOptions,该参数用于控制模块加载的生命周期,这四个Contributor分别对应模块加载生命周期的接口,

 

 

 

 再看看核心Module的抽象

 到这里肯定很多人很困惑,所以这里跳过一些流程,看下ModuleManager如何处理,如下

 释出Contributor集合

 

Contributor的作用很明显,模块加载生命周期中你可以执行的一些方法.这些方法会拿到一个ServiceProvider,即你可以操作DI,完成一些关键服务的操作.

关于模块加载的生命周期方法有哪些,如下

 每个接口对应一个生命周期,这和老版Abp的设计也完全不同.优缺点暂时没发现.

接着,如下:

 调用ModuleLoader单例实例,执行加载模块的方法.核心算法和老版Abp一样,这里稍微解释下,

 核心点如下:

(1)、加载启动模块所有依赖的模块,并设置依赖项,最后生成IAbpModuleDescriptor集合

 (2)、模块进行拓扑排序,进行循环依赖检测

 ok,下面开始解析核心点源码

通过DependsOnAttribute特性来处理模块间的依赖关系.核心代码如下:

 拿到当前类型的DependsOnAttribute特性,解析其内部的类型,加入到dependencies依赖类型集合.所以表示模块间的依赖关系根据如上代码可以得出两种模式,如下:

 常用的是第二种.

通过上面的方法拿到所有的依赖类型集合之后,执行下面的递归方法

 这样就可以遍历出所有的启动模块以来的所有模块.同时去除了重复的模块.最后遍历所有的模块生成如下类型的实例

 模块实例的生命周期为单例,如下图:

接着开始处理启动参数中配置的插件模块

 

 插件模块的三种添加方式如下

public static class PlugInSourceListExtensions{public static void AddFolder([NotNull] this PlugInSourceList list,[NotNull] string folder,SearchOption searchOption = SearchOption.TopDirectoryOnly){Check.NotNull(list, nameof(list));list.Add(new FolderPlugInSource(folder, searchOption));}public static void AddTypes([NotNull] this PlugInSourceList list,params Type[] moduleTypes){Check.NotNull(list, nameof(list));list.Add(new TypePlugInSource(moduleTypes));}public static void AddFiles([NotNull] this PlugInSourceList list,params string[] filePaths){Check.NotNull(list, nameof(list));list.Add(new FilePlugInSource(filePaths));}}

这边只介绍一种,其余核心流程都一样,如下:

FolderPlugInSource添加插件类型,其核心参数如下:

直接给文件夹路径+名称,扫描下面的插件程序集,并进行程序集过滤,核心的过滤方法如下:

 最后,返回实现了AbpModule的核心模块类型

 ok,接着回到模块加载系统的加载插件方法,如下:

 

 ok,这里可以发现亮点

1、你可以同时添加多种形式的插件宿主,可以是文件夹下所有的插件程序集、可以是程序集解决方案、也可以是一个指定的程序集文件.Abp暂时提供了这三种,当然如果你有实力,也可以编写远程调用程序集插件.

2、和模块加载系统完成了集成,和上面的流程一样,加载出所有启动模块依赖的类型,并写入DI

 ok,到这里插件模块介绍完毕.最后和普通模块一样生成IAbpModuleDescriptor集合

接着,拿到所有的模块集合之后(包括插件),开始设置所有模块间的依赖关系,如下,细心的会发现上面的

 

 中有依赖集合.下面的代码就是整理这个关系的.

 

 这里,逻辑很简单,就不介绍了,直接跳过,主要是通过DependsOnAttribute特性来实现.

接下去介绍核心点二模块进行拓扑排序,进行循环依赖检测

此时,我们拿到了一个完整的模块集合,内部的依赖关系也已经初步执行好.

 

 核心代码如下,关于拓扑排序(算法的核心逻辑自行查阅代码,主要内容是按照依赖关系依次加入到集合,后期可一次执行,这样就可以集成生命周期),防止循环依赖就不说了,接着,将启动模块放到最后为了配合模块生命周期方法的执行.

ok,到这里两个核心点介绍完毕.

接下去.如下代码

 

 生成如下上下文,并单例写入DI

这个Item属性醉了,个人感觉没什么用,因为下面这个for循环

 接着执行如下代码

 所以这两个生命周期接口执行的时间节点一定要记住.同时上下文会给你DI容器,方便你进行任何必须的类型操作.

接着

 将当前模块类型对应的程序集中所有的类型写入DI,默认的注入规则上面已经介绍,默认的注册器类型为DefaultConventionalRegistrar.同时执行生命周期接口IZcfModule.

到这里已经执行的三个模块生命周期接口如下:

 切记其执行的节点.

 接着开始初始化模块系统,注意,这边我跳过了DI容器切换的的内容(关于DI容器切换的源码分析后续的博文会介绍),代码如下:

 

 

 从DI中释出单例ModuleManager类,执行如下初始化方法

 

 

 这段代码进行简单的模块加载日志记录,后面的核心代码上面说过,执行预定义的模块生命周期方法,对应如下接口:

 

 

执行这四个接口必须实现的方法,当然在AbpModule中都以virtual标记,所以你可以按照顺序一次进行一些类型操作.但是这几个生命周期函数,上下文只提供ServiceProvider,

功能做了限制.其余三个生命周期接口提供的是IServiceCollection实例,所以他们之间还是有差别的,除了执行顺序之外.

 

ok,到这里abp vnext2.0的核心模块记载系统核心流程源码分析结束了,纯属个人理解,能力有限,有问题请指正!

下一篇会介绍vnext如何完成整个DI切换,换成autofac或者其他容器.以及如何和模块加载系统结合.

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

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

相关文章

最大堆和最小堆排序

最大堆和最小堆排序 原理参考趣学数据结构 代码 #include<stdio.h> #include<stdlib.h> int r[] { -1,1,4,590,4,2,8,7,5,89,67,5,2,1,67,86,54 };//存储要排序的数,第一个元素不存储元素赋值为-1 int length sizeof(r) / sizeof(int);//待排序的数的个数 void s…

wordList01

wordList one 如果存在什么问题请批评指正&#xff01;谢谢

《ASP.NET Core 微服务实战》-- 读书笔记(第4章)

第 4 章 后端服务现实中的服务不可能处于真空之中&#xff0c;大多数服务都需要与其他服务通信才能完成功能。我们将这些支持性服务称为后端服务&#xff0c;接下来我们将通过创建一个新的服务并修改之前的团队服务与这个服务通信&#xff0c;以探索如何创建并消费后端服务。微…

WordList02

WordList 2 如果存在什么问题欢迎批评指正&#xff01;谢谢&#xff01;

CentOS7 安装 Jenkins( 构建 Vue 和 dotNET Core )

之前的自动构建工具 Jenkins 是部署在公司内网的 Windows 服务器上&#xff0c;现在武汉处于非常时期&#xff0c;兄弟们都在家自我隔离&#xff0c;为了远程提交的代码能自动构建&#xff0c;需要在外网的 CentOS 服务器上搭建 Jenkins 环境来进行构建工作。目的产品采用前后端…

shell(希尔排序)

shell&#xff08;希尔排序&#xff09; 原理:参考趣学数据结构 代码&#xff1a; #include<stdio.h> #include<stdlib.h> void shellInsert(int b[],int dk,int length) {//希尔排序以dk的增量插入int j;for (int i 1 dk; i < length-1; i) {b[0] b[i];//哨…

[蓝桥杯2019初赛]等差数列-数列

解题思路: 给你n个数&#xff0c;是某个等差数列的一部分&#xff0c;问该等差数列最小有几项&#xff1f;&#xff1a;((最大数−最小数)/d)1((最大数-最小数)/d)1((最大数−最小数)/d)1,其中d是该等差数列所有&#xff08;所有已知数与最小数差值&#xff09;的最大公因数&am…

Dynatrace成功扩展kubernetes全栈可观察性

软件情报公司Dynatrace&#xff08;NYSE&#xff1a;DT&#xff09;在Perform 2020会议上宣布了对Kubernetes支持的新增强功能。Dynatrace可解释的AI引擎DavisTM现在自动获取其他Kubernetes事件和指标&#xff0c;使其能够在整个Kubernetes集群&#xff0c;容器和工作负载的堆栈…

wordList3

wordList3 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

[蓝桥杯2019初赛]矩形切割-找规律

代码如下&#xff1a; #include <iostream> using namespace std;int main() {int a, b;int ans 1;cin >> a >> b;while (1) {if (a 1 && b 1 || a b)break;if ( a > b) {int c b;b a;a c;}b b - a;ans;}cout << ans << end…

K8S水平伸缩器 - 自动伸缩微服务实例数量

作者&#xff1a;justmine头条号&#xff1a;大数据达摩院微信公众号&#xff1a;大数据处理系统创作不易&#xff0c;在满足创作共用版权协议的基础上可以转载&#xff0c;但请以超链接形式注明出处。为了方便大家阅读&#xff0c;可以关注头条号或微信公众号&#xff0c;后续…

[蓝桥杯2019初赛]质数-质数筛or 水题

法一&#xff1a; 代码如下&#xff1a; #include <iostream> #include <cmath> using namespace std;bool check(int x) {for (int i 2; i < sqrt(x); i)if (x % i 0)return false;return true; }int main() {int n;int ans 0;for (int i 2; i; i) {if (c…

SqlServer 利用游标批量更新数据

SqlServer 利用游标批量更新数据Intro游标在有时候会很有用&#xff0c;在更新一部分不多的数据时&#xff0c;可以很方便的更新数据&#xff0c;不需要再写一个小工具来做了&#xff0c;直接写 SQL 就可以了Sample下面来看一个实际示例&#xff1a;-- 声明字段变量 DECLARE Re…

math:线性代数之行列式

math&#xff1a;线性代数之行列式 提供解题的方法总结 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢!

[蓝桥杯2019初赛]旋转-找规律

代码如下&#xff1a; #include <iostream> using namespace std; const int N 110; int a[N][N];int main() {int n, m;cin >> n >> m;for (int i 1; i < n; i)for (int j 1; j < m; j)cin >> a[i][j];for (int i 1; i < m; i){for (in…

《ASP.NET Core 微服务实战》-- 读书笔记(第5章)

第 5 章 创建数据服务选择一种数据存储由于我坚持要尽可能的跨平台&#xff0c;所以我决定选用 Postgres&#xff0c;而不用 SQL Server 以照顾 Linux 或 Mac 电脑的读者构建 Postgres 仓储在本节&#xff0c;我们要升级位置服务让它使用 Postgres为了完成这一过程&#xff0c;…

腾飞答不忘初心的三个问题

去年的时候有同学在微信上问了我3个问题&#xff0c;我觉得非常有代表性&#xff0c;当时没有回答。主要是通过简短的语言无法全面表述可能会产生歧义&#xff0c;所以决定专门写一篇文章来表达一下我的想法&#xff0c;需要说明的是以下内容只是以我个人的经验给大家一些参考。…

wordList04

wordList04 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

用 C# 写一个 Redis 数据同步小工具

用 C# 写一个 Redis 数据同步小工具Intro为了实现 redis 的数据迁移而写的一个小工具&#xff0c;将一个实例中的 redis 数据同步到另外一个实例中。(原本打算找一个已有的工具去做&#xff0c;找了一个 nodejs 的小工具&#xff0c;结果折腾了好久都没装上。。。于是就自己写了…

数据结构----冒泡排序

数据结构----冒泡排序 原理&#xff1a;参考趣学数据结构 代码&#xff1a; #include<stdio.h> #include<stdlib.h> void bubbleSort(int a[], int length) {//冒泡排序for (int i 0; i < length - 1; i) {//趟数for (int j 0; j < length - i - 1; j) {if…