Alby's blog

世上没有巧合,只有巧合的假象。

0%

Abp 源码分析(2):模块

一、概述

Abp 本身是一个包含许多 nuget 包的模块化框架。它还提供了一个完整的基础架构来开发你自己的具有实体、服务、数据库集成、AP、UI 组件等等功能的应用程序模块。
每个模块都应该定义一个模块类。模块类指实现了 IAbpModule 接口的非抽象非泛型类。定义模块类的最简单方法是创建一个派生自 AbpModule 的类,AbpModule 是一个实现了 AbpModule 接口的抽象非泛型类。

二、IAbpModule

1
2
3
4
5
6
7
8
// File: src\Volo.Abp.Core\Volo\Abp\Modularity\IAbpModule.cs
namespace Volo.Abp.Modularity
{
    public interface IAbpModule
    {
        void ConfigureServices(ServiceConfigurationContext context);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// File: src\Volo.Abp.Core\Volo\Abp\Modularity\ServiceConfigurationContext.cs
namespace Volo.Abp.Modularity
{
public class ServiceConfigurationContext
{
public IServiceCollection Services { get; }

public IDictionary<string, object> Items { get; }

/// <summary>
/// Gets/sets arbitrary named objects those can be stored during
/// the service registration phase and shared between modules.
///
/// This is a shortcut usage of the <see cref="Items"/> dictionary.
/// Returns null if given key is not found in the <see cref="Items"/> dictionary.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public object this[string key]
{
get => Items.GetOrDefault(key);
set => Items[key] = value;
}

public ServiceConfigurationContext([NotNull] IServiceCollection services)
{
Services = Check.NotNull(services, nameof(services));
Items = new Dictionary<string, object>();
}
}
}

ServiceConfigurationContext 类比较简单,就 2 个属性。在加载模块时会为每个模块类创建一个 ServiceConfigurationContext 实例并调用其 ConfigureServices 方法。

三、AbpModule

该部分内容主要来自官方文档。

1、ConfigureServices

实现自 IAbpModule 接口的方法。

1
2
3
4
5
6
// File: src\Volo.Abp.Core\Volo\Abp\Modularity\AbpModule.cs
public virtual void ConfigureServices(ServiceConfigurationContext context)
{

}

2、配置服务前和后

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

1
2
3
4
5
6
7
8
9
10
public virtual void PreConfigureServices(ServiceConfigurationContext context)
{

}

public virtual void PostConfigureServices(ServiceConfigurationContext context)
{

}

3、应用程序初始化

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

1
2
3
4
public virtual void OnApplicationInitialization(ApplicationInitializationContext context)
{

}

4、应用程序初始化前和后

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

1
2
3
4
5
6
7
8
9
public virtual void OnPreApplicationInitialization(ApplicationInitializationContext context)
{

}

public virtual void OnPostApplicationInitialization(ApplicationInitializationContext context)
{

}

5、应用程序关闭

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

1
2
3
4
public virtual void OnApplicationShutdown(ApplicationShutdownContext context)
{

}

6、模块依赖

在模块化应用程序中,一个模块依赖于另一个或几个模块并不罕见.如果一个 Abp 模块依赖于另一个模块,它必须声明 DependsOnAttribute 特性。

四、AbpModuleDescriptor

AbpModuleDescriptor 包含的模块的类型、所在程序集合、实例、是否以插件形式加载和依赖的其他模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// File: src\Volo.Abp.Core\Volo\Abp\Modularity\AbpModuleDescriptor.cs
namespace Volo.Abp.Modularity
{
public class AbpModuleDescriptor : IAbpModuleDescriptor
{
/// <summary>
/// 模块类型
/// </summary>
public Type Type { get; }

/// <summary>
/// 模块类型所在程序集
/// </summary>
public Assembly Assembly { get; }

/// <summary>
/// 模块实例
/// </summary>
public IAbpModule Instance { get; }

/// <summary>
/// 是否以插件形式加载
/// </summary>
public bool IsLoadedAsPlugIn { get; }

/// <summary>
/// 依赖的其他模块
/// </summary>
public IReadOnlyList<IAbpModuleDescriptor> Dependencies => _dependencies.ToImmutableList();
private readonly List<IAbpModuleDescriptor> _dependencies;

public AbpModuleDescriptor(
[NotNull] Type type,
[NotNull] IAbpModule instance,
bool isLoadedAsPlugIn)
{
Check.NotNull(type, nameof(type));
Check.NotNull(instance, nameof(instance));

if (!type.GetTypeInfo().IsAssignableFrom(instance.GetType()))
{
throw new ArgumentException($"Given module instance ({instance.GetType().AssemblyQualifiedName}) is not an instance of given module type: {type.AssemblyQualifiedName}");
}

Type = type;
Assembly = type.Assembly;
Instance = instance;
IsLoadedAsPlugIn = isLoadedAsPlugIn;

_dependencies = new List<IAbpModuleDescriptor>();
}

public void AddDependency(IAbpModuleDescriptor descriptor)
{
_dependencies.AddIfNotContains(descriptor);
}

public override string ToString()
{
return $"[AbpModuleDescriptor {Type.FullName}]";
}
}
}

五、ModuleLoader

ModuleLoader 的核心方法是 LoadModules 方法,该方法主要进行如下操作:

1、获取模块描述符

从启动模块(StartupModule)开始递归查找所有依赖的模块类型。并根据模块类型创建 模块描述符 (Module Descriptor)。在此过程中会创建模块实例,实例会以单例服务形式注入到 IoC 中,也会存入模块描述符中。基于模块描述符集合,再设置各个模块描述符所以依赖的其他模块描述符(AbpModuleDescriptorIReadOnlyList<IAbpModuleDescriptor> Dependencies)。
使用 AbpModuleHelper.FindAllModuleTypes 查找模块类型,其根据标注在模块类定义之上的 DependsOnAttributeIDependedTypesProvider) 所标注的类型进行递归查找。不会将同类型的模块类型重复收集。
创建模块类型实例是使用 Activator.CreateInstance(Type type) 方法,所以模块类型需提供无参构造函数(或无任何显示声明的构造函数)。

模块描述符的定义见 AbpModuleDescriptor 类。

2、模块排序

将模块集合进行拓扑排序,然后将启动模块置在集合的第一位。

3、注册模块服务

首先针对实现了 IPreConfigureServices 接口的模块,调用 PreConfigureServices 方法。然后针对继承自 AbpModule 的模块,如果 SkipAutoServiceRegistration 没有设置为 true(详见官方文档:依照约定的注册),将其所在程序集的某些服务进行自动注册。最后针对实现了 IPostConfigureServices 接口的模块,调用 PostConfigureServices 方法。

备注:

  1. AbpModule 实现了 IPreConfigureServices 和 IPostConfigureServices 接口。
  2. Conventional registrar 决定了哪些类会注册入 IoC。目前有 DefaultConventionalRegistrar、AbpAspNetCoreMvcConventionalRegistrar 和 AbpFluentValidationConventionalRegistrar。DefaultConventionalRegistrar 注册声明了 DependencyAttribute 特性和实现了接口 ITransientDependency、ISingletonDependency 或 IScopedDependency 接口的服务;AbpAspNetCoreMvcConventionalRegistrar 注册 PageModel、Controller 和 ViewComponent 。AbpFluentValidationConventionalRegistrar 注册 IValidator<T> 服务。

4、返回值

ModuleLoder 返回模块描述符数组。构造 AbpApplicationBase 实例会加载模块,即调用 LoadModules 方法,方法的返回值会赋给 AbpApplicationBase 的 Modules 属性

六、ModuleManager

ModuleManager 用于管理模块的生命周期。在应用程序初始化、应用程序初始化前和后以及应用程序关闭,分别调用各个模块的 OnApplicationInitialization、OnPreApplicationInitialization 和 OnPostApplicationInitialization 以及 OnApplicationShutdown 方法。
额外的,ModuleManager 能够以日志输出应用程序使用了哪些模块。

参考资料