一、模块化应用

 1、继承AbpModule

每个模块都应该定义一个模块类.定义模块类的最简单方法是创建一个派生自AbpModule的类,如下所示:

 2、配置依赖注入和其他模块---ConfigService方法

在ConfigService中注入你用到的所有服务

你可以按照Microsoft的文档中的说明逐个注册依赖项.但ABP有一个依照约定的依赖注册系统,可以自动注册程序集中的所有服务.有关依赖项注入系统的更多信息.

你也可以通过这种方式配置其他服务和模块.例:

public override void ConfigureServices(ServiceConfigurationContext context)
{
//为应用程序配置默认的连接字符串
Configure<AbpDbConnectionOptions>(options =>
{
options.ConnectionStrings.Default = "......";
});
}

AbpModule类还定义了PreConfigureServices和PostConfigureServices方法用来在ConfigureServices之前或之后覆盖和编写你的代码.请注意,在这些方法中编写的代码将在所有其他模块的ConfigureServices方法之前/之后执行.

3、配置中间件管道---OnApplicationInitialization方法

一旦配置了所有模块的所有服务,应用程序就会通过初始化所有模块来启动.在此阶段,你可以从IServiceProvider中获取服务,因为这时它已准备就绪且可用.

 public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var myService = context.ServiceProvider.GetService<MyService>();
myService.DoSomething();
}

OnApplicationInitialization通常由启动模块用于构建ASP.NET Core应用程序的中间件管道.例:

 public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment(); if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseMvcWithDefaultRoute();
}

如果模块需要,你还可以执行启动逻辑

应用程序初始化前和后

AbpModule类还定义了OnPreApplicationInitialization和OnPostApplicationInitialization方法用来在OnApplicationInitialization之前或之后覆盖和编写你的代码.请注意,在这些方法中编写的代码将在所有其他模块的OnApplicationInitialization方法之前/之后执行.

应用程序关闭

最后,如果要在应用程序关闭时执行某些代码,你可以覆盖OnApplicationShutdown方法.

4、模块依赖

在模块化应用程序中,一个模块依赖于另一个或几个模块并不罕见.如果一个Abp模块依赖于另一个模块,它必须声明[DependsOn]特性,如下所示:

你可以根据需要使用多个DependsOn特性或将多个模块类型传递给单个DependsOn特性.

依赖模块可能依赖于另一个模块,但你只需要定义直接依赖项.ABP在启动时会调查应用程序的依赖关系,并以正确的顺序初始化/关闭模块.

 5、项目引入模块

在项目启动文件 startup中引入模块就可以了

二、模块化原理

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

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

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

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

三、模块化源码解读

1、在模块化应用部分我们看到模块的启动是以项目中Startup.cs如下的段代码开始的,我们就从这里开始进入模块化的源码解读吧

public void ConfigureServices(IServiceCollection services)
{
services.AddApplication<FileManagementServiceHostModule>(); }

2、其实AddApplication<T>() 这个方法是一个扩展方法写在类ServiceCollectionApplicationExtensions中,方法中泛型参数TStartupModule就是模块的类型,可以看到泛型约束是要实现IAbpModule接口

,这就是我们自定义的模块FileManagementServiceHostModule必须要继承AbpModule的原因。

3、在这个方法中调用了AbpApplicationFactory类的CreateAsync方法,那我们现在进入这个类看看

此方法就是用我们自定义好的模块FileManagementServiceHostModule创建出了一个实现了IAbpApplicationWithExternalServiceProvider的对象AbpApplicationWithExternalServiceProvider

4、那我们看看IAbpApplicationWithExternalServiceProvider

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

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

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

5、以上分析可知,模块的初始化是从IAbpApplication接口的实现类AbpApplicationBase开始的

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

那我们先来详细看看构造器中作了哪些事

先看看services.AddCoreServices();

进入此扩展方法发现注入配置文件、日志、国际化等服务.接着看AddCoeAbpServices方法

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

还进行了配置文件的初始化

再看看services.AddAssemblyOf<IAbpApplication>();是在做什么

从DI中读取程序集注册规则类列表,如果没有,则写入默认的程序集注册规则类

再看看接下来的代码

此处是在向DI中预先写入AbpModuleLifecycleOptions,该参数用于控制模块加载的生命周期,这四个Contributor分别对应模块加载生命周期的接口

让我们再次回来AbpApplicationBase类的构造函数中中看看比较核心的一段代码Modules = LoadModules(services, options);

作用是加载所有模块。

进入其核心类ModuleLoader中查看

到此,本模块和它所依赖的所有的模块都初始化完了,继续往下面看

看看这个configureServices()又在做什么呢,进入此方法体中

 public virtual void ConfigureServices()
{
CheckMultipleConfigureServices();
// 构造一个服务上下文,并将其添加到 IoC 容器当中。
var context = new ServiceConfigurationContext(Services);
Services.AddSingleton(context); foreach (var module in Modules)
{
if (module.Instance is AbpModule abpModule)
{
abpModule.ServiceConfigurationContext = context;
}
} // 执行预加载方法 PreConfigureServices。
foreach (var module in Modules.Where(m => m.Instance is IPreConfigureServices))
{
try
{
((IPreConfigureServices)module.Instance).PreConfigureServices(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IPreConfigureServices.PreConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
} var assemblies = new HashSet<Assembly>(); // 执行初始化方法 ConfigureServices。
foreach (var module in Modules)
{
if (module.Instance is AbpModule abpModule)
{
if (!abpModule.SkipAutoServiceRegistration)
{
var assembly = module.Type.Assembly;
if (!assemblies.Contains(assembly))
{
Services.AddAssembly(assembly);
assemblies.Add(assembly);
}
}
} try
{
module.Instance.ConfigureServices(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IAbpModule.ConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
} // 执行初始化完成方法 PostConfigureServices。
foreach (var module in Modules.Where(m => m.Instance is IPostConfigureServices))
{
try
{
((IPostConfigureServices)module.Instance).PostConfigureServices(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IPostConfigureServices.PostConfigureServices)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
// 将服务上下文置为 NULL。
foreach (var module in Modules)
{
if (module.Instance is AbpModule abpModule)
{
abpModule.ServiceConfigurationContext = null;
}
} _configuredServices = true;
}

以上动作都是在 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)
{
// 遍历所有的模块,将模块实例传入具体的 Contributor,方便在其内部调用具体的生命周期方法。
foreach (var module in _moduleContainer.Modules)
{
Contributor.Initialize(context, module.Instance);
}
} _logger.LogInformation("Initialized all modules.");
}

ABP vNext系列文章01---模块化的更多相关文章

  1. ABP vNext系列文章03---依赖注入

    一.依赖注入的类型注册 ABP的依赖注入系统是基于Microsoft的依赖注入扩展库(Microsoft.Extensions.DependencyInjection nuget包)开发的.因此,它的 ...

  2. 2019 年起如何开始学习 ABP 框架系列文章-开篇有益

    2019 年起如何开始学习 ABP 框架系列文章-开篇有益 [[TOC]] 本系列文章推荐阅读地址为:52ABP 开发文档 https://www.52abp.com/Wiki/52abp/lates ...

  3. Learn-JavaScript-with-MDN 系列文章: 01. var & let & const 对比

    Learn-JavaScript-with-MDN 系列文章: 01. var & let & const 对比 var & let & const 区别 https: ...

  4. 一步一步学习ABP项目系列文章目录

    1.概述 基于DDD的.NET开发框架 - ABP初探 基于DDD的.NET开发框架 - ABP分层设计 基于DDD的.NET开发框架 - ABP模块设计 基于DDD的.NET开发框架 - ABP启动 ...

  5. [Abp vNext 源码分析] - 7. 权限与验证

    一.简要说明 在上篇文章里面,我们在 ApplicationService 当中看到了权限检测代码,通过注入 IAuthorizationService 就可以实现权限检测.不过跳转到源码才发现,这个 ...

  6. [Abp vNext 入坑分享] - 8.Redis与Refit的接入

    前言 本章结束之后,这个abp vnext系列算是初步完结了,基础的组件都已经接入了.如果各位还需要其它的组件的话,可以自己按需要进行接入使用.其实这个只是一个基础的框架,可以自己根据需要进行变通的. ...

  7. Abp vNext 番外篇-疑难杂症丨浅谈扩展属性与多用户设计

    说明 Abp vNext基础篇的文章还差一个单元测试模块就基本上完成了我争取10.1放假之前给大家赶稿出来,后面我们会开始进阶篇,开始拆一些东西,具体要做的事我会单独开一个文章来讲 缘起 本篇文章缘起 ...

  8. [Abp vNext 源码分析] - 11. 用户的自定义参数与配置

    一.简要说明 文章信息: 基于的 ABP vNext 版本:1.0.0 创作日期:2019 年 10 月 23 日晚 更新日期:暂无 ABP vNext 针对用户可编辑的配置,提供了单独的 Volo. ...

  9. [Abp vNext 源码分析] - 12. 后台作业与后台工作者

    一.简要说明 文章信息: 基于的 ABP vNext 版本:1.0.0 创作日期:2019 年 10 月 24 日晚 更新日期:暂无 ABP vNext 提供了后台工作者和后台作业的支持,基本实现与原 ...

  10. consul系列文章02---替换掉.netcore的配置文件

    如果是开发微服务的项目,多个服务的配置管理起来比较麻烦,需要集中管理,也就是需要有配置中心: consul集成配置中心的思路:读取配置文件时不在从本地的应用中读取,而是从consul的KEY/valu ...

随机推荐

  1. 从“概念”到“应用”,字节跳动基于 DataLeap 的 DataOps 实践

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 近日,火山引擎数智平台 VeDI Meetup「超话数据」在深圳举办,来自火山引擎的产品专家分享了字节跳动基于 D ...

  2. Snack3 3.1.10的新特性及应用

    <dependency> <groupId>org.noear</groupId> <artifactId>snack3</artifactId& ...

  3. peewee 操作 sqlite 锁表问题分析

    在使用python orm 框架 peewee 操作数据库时时常会抛出以一个异常,具体的报错就是 database is locked 初步了解是因为sqlite锁的颗粒度比较大,是库锁.当一个连接在 ...

  4. Axure RP 9 下载、破解激活教程、最新激活码,亲测可用

    本教程适用于 Axure RP 9 Enterprise/Pro/Team 的激活.破解,最新激活码,适用于 windows, mac系统,仅用于学习 简介 Axure RP 9.0 破解版是一款功能 ...

  5. Java 键值对数据本地保存与读取

    代码: package com.sux.demo.utils; import com.alibaba.fastjson.JSONObject; import java.io.File; import ...

  6. 【每天一个不会秃头的前端案例】CSS + JS 实现早安,晚安动画

    从橘子学姐那边学来的 早安,晚安动画(CSS + js) 成果展示 先看成果. 通过点击太阳实现日夜的动画平滑交替 Movie 这里就不多说什么了,直接贴一下代码 HTML部分 <!DOCTYP ...

  7. 第九届蓝桥杯(2018)C/C++大学A组省赛题解

    第一题:分数 1/1 + 1/2 + 1/4 + 1/8 + 1/16 + - 每项是前一项的一半,如果一共有20项, 求这个和是多少,结果用分数表示出来. 类似:3/2 当然,这只是加了前2项而已. ...

  8. wav文件头信息

    概述 最近在对接百度TTS的python接口,对接的过程中发现一些问题,记录下解决方案. 百度TTS接口返回的音频数据格式有4种,分别是mp3,pcm-16k,pcm-8k,wav(pcm-16k). ...

  9. Liunx常用操作(八)-sed命令详细说明

    一.sed简介 sed是一种流编编器,它是文本处理中非常中的工具,能够完美的配合正则表达式便用,功物能不同凡响. 处理时,把当前处理的行存储在临时缓冲区中,称为"模式空间"( oa ...

  10. Kubernetes security context capability

    注:以下内容基于经验主义,不一定对. Linux capability Linux 中,root 作为特权用户,具有执行所有应用的能力.而普通用户只能执行普通应用.如果普通用户需要执行特权应用,需要进 ...