从入口看起:AbpApplicationFactory.Create
csharp
using var app = AbpApplicationFactory.Create<BusinessModule>(options =>
{
options.UseAutofac();
});
app.Initialize();
这行代码的背后发生了什么?直接看源码 AbpApplicationFactory.cs:
csharp
public static IAbpApplicationWithInternalServiceProvider Create<TStartupModule>(
Action<AbpApplicationCreationOptions>? optionsAction = null)
{
return new AbpApplicationWithInternalServiceProvider(typeof(TStartupModule), optionsAction);
}
它创建了 AbpApplicationWithInternalServiceProvider,这个类在构造时做了两件关键的事:
- 创建
ModuleLoader并调用LoadModules加载所有模块 - 按拓扑排序确定模块初始化顺序
深度解析 ModuleLoader.LoadModules
源码位于 framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs,核心逻辑只有三行:
csharp
public IAbpModuleDescriptor[] LoadModules(IServiceCollection services, Type startupModuleType, PlugInSourceList plugInSources)
{
var modules = GetDescriptors(services, startupModuleType, plugInSources); // 1. 发现所有模块
modules = SortByDependency(modules, startupModuleType); // 2. 拓扑排序
return modules.ToArray(); // 3. 返回有序列表
}
第一步:递归发现
GetDescriptors 内部分两步:
FillModules ------从入口模块开始,递归查找所有 [DependsOn] 依赖:
csharp
// AbpModuleHelper.cs
private static void AddModuleAndDependenciesRecursively(List<Type> moduleTypes, Type moduleType, ...)
{
moduleTypes.Add(moduleType);
foreach (var dependedModuleType in FindDependedModuleTypes(moduleType))
{
AddModuleAndDependenciesRecursively(moduleTypes, dependedModuleType, ...);
}
}
FindDependedModuleTypes 通过反射读取当前类上的所有 [DependsOn] 属性:
csharp
public static List<Type> FindDependedModuleTypes(Type moduleType)
{
var dependencyDescriptors = moduleType.GetCustomAttributes().OfType<IDependedTypesProvider>();
foreach (var descriptor in dependencyDescriptors)
{
dependencies.AddIfNotContains(descriptor.GetDependedTypes());
}
}
这意味着不仅 [DependsOn] 会被识别,任何实现了 IDependedTypesProvider 的自定义属性都能用来声明依赖。
第二步:拓扑排序
csharp
protected virtual List<IAbpModuleDescriptor> SortByDependency(List<IAbpModuleDescriptor> modules, Type startupModuleType)
{
var sortedModules = modules.SortByDependencies(m => m.Dependencies);
sortedModules.MoveItem(m => m.Type == startupModuleType, modules.Count - 1);
return sortedModules;
}
这里使用了 SortByDependencies 扩展方法(来自 Volo.Abp.Core 的工具类),是一个标准的拓扑排序实现。排序后,入口模块被移到最后一位,保证它最后初始化。
如果在模块依赖图中存在循环依赖,拓扑排序会抛出异常------这在启动时就能发现,不会让问题带到运行时。
第三步:创建模块实例
csharp
protected virtual IAbpModule CreateAndRegisterModule(IServiceCollection services, Type moduleType)
{
var module = (IAbpModule)Activator.CreateInstance(moduleType)!;
services.AddSingleton(moduleType, module); // 每个模块以 Singleton 注册到 DI
return module;
}
每个模块实例被注册为 Singleton,这意味着模块在整个应用生命周期中只创建一次。
生命周期钩子的调用顺序
模块加载完毕后调用 Initialize,进入生命周期阶段。在 AbpApplicationBase.cs 中:
阶段 1:ConfigureServices
for each module in sortedModules:
module.Instance.PreConfigureServices(context)
for each module in sortedModules:
module.Instance.ConfigureServices(context)
for each module in sortedModules:
module.Instance.PostConfigureServices(context)
阶段 2:构建 IServiceProvider
阶段 3:OnApplicationInitialization
for each module in sortedModules:
module.Instance.OnPreApplicationInitialization(context)
module.Instance.OnApplicationInitialization(context)
module.Instance.OnPostApplicationInitialization(context)
注意:三个阶段都按排序后的顺序执行,入口模块永远在最后。
实战要点
1. 模块不可循环依赖
如果模块 A 依赖 B,B 又依赖 A,启动时直接抛出 AbpException。这比运行时发现要好得多。
2. AdditionalAssembly 的用途
除了 [DependsOn],还有 [AdditionalAssembly] 属性。它不声明依赖,只是告诉框架"这个模块的程序集也加载进来"。用于解决模块类在另一个程序集中的场景。
3. 模块实例是 Singleton
模块中定义的状态在应用生命周期内保持。不要在模块中存储请求级别的数据。
4. PreConfigureServices 的应用
PreConfigureServices 可以用于设置默认选项,让其他模块在 ConfigureServices 中可以覆盖:
csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
// 设置默认值,允许下游模块覆盖
context.Services.AddSingleton(new DefaultOptions { ... });
}
验证结果
运行练习示例,输出清晰展示了顺序:
[LoggingModule] ConfigureServices ← 被依赖的模块先执行
[DataAccessModule] ConfigureServices
[BusinessModule] ConfigureServices ← 入口模块最后执行
即使打乱 [DependsOn] 的书写顺序,输出顺序不变。原因就是 SortByDependencies 的拓扑排序保证了依赖顺序,和代码中的书写顺序无关。