ABP 模块系统源码学习:启动时模块是如何加载和排序的
2026/8/2 3:29:28 网站建设 项目流程

从入口看起:AbpApplicationFactory.Create

usingvarapp=AbpApplicationFactory.Create<BusinessModule>(options=>{options.UseAutofac();});app.Initialize();

这行代码的背后发生了什么?直接看源码AbpApplicationFactory.cs

publicstaticIAbpApplicationWithInternalServiceProviderCreate<TStartupModule>(Action<AbpApplicationCreationOptions>?optionsAction=null){returnnewAbpApplicationWithInternalServiceProvider(typeof(TStartupModule),optionsAction);}

它创建了AbpApplicationWithInternalServiceProvider,这个类在构造时做了两件关键的事:

  1. 创建ModuleLoader并调用LoadModules加载所有模块
  2. 按拓扑排序确定模块初始化顺序

深度解析 ModuleLoader.LoadModules

源码位于framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLoader.cs,核心逻辑只有三行:

publicIAbpModuleDescriptor[]LoadModules(IServiceCollectionservices,TypestartupModuleType,PlugInSourceListplugInSources){varmodules=GetDescriptors(services,startupModuleType,plugInSources);// 1. 发现所有模块modules=SortByDependency(modules,startupModuleType);// 2. 拓扑排序returnmodules.ToArray();// 3. 返回有序列表}

第一步:递归发现

GetDescriptors内部分两步:

FillModules——从入口模块开始,递归查找所有[DependsOn]依赖:

// AbpModuleHelper.csprivatestaticvoidAddModuleAndDependenciesRecursively(List<Type>moduleTypes,TypemoduleType,...){moduleTypes.Add(moduleType);foreach(vardependedModuleTypeinFindDependedModuleTypes(moduleType)){AddModuleAndDependenciesRecursively(moduleTypes,dependedModuleType,...);}}

FindDependedModuleTypes通过反射读取当前类上的所有[DependsOn]属性:

publicstaticList<Type>FindDependedModuleTypes(TypemoduleType){vardependencyDescriptors=moduleType.GetCustomAttributes().OfType<IDependedTypesProvider>();foreach(vardescriptorindependencyDescriptors){dependencies.AddIfNotContains(descriptor.GetDependedTypes());}}

这意味着不仅[DependsOn]会被识别,任何实现了IDependedTypesProvider的自定义属性都能用来声明依赖。

第二步:拓扑排序

protectedvirtualList<IAbpModuleDescriptor>SortByDependency(List<IAbpModuleDescriptor>modules,TypestartupModuleType){varsortedModules=modules.SortByDependencies(m=>m.Dependencies);sortedModules.MoveItem(m=>m.Type==startupModuleType,modules.Count-1);returnsortedModules;}

这里使用了SortByDependencies扩展方法(来自 Volo.Abp.Core 的工具类),是一个标准的拓扑排序实现。排序后,入口模块被移到最后一位,保证它最后初始化。

如果在模块依赖图中存在循环依赖,拓扑排序会抛出异常——这在启动时就能发现,不会让问题带到运行时。

第三步:创建模块实例

protectedvirtualIAbpModuleCreateAndRegisterModule(IServiceCollectionservices,TypemoduleType){varmodule=(IAbpModule)Activator.CreateInstance(moduleType)!;services.AddSingleton(moduleType,module);// 每个模块以 Singleton 注册到 DIreturnmodule;}

每个模块实例被注册为 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中可以覆盖:

publicoverridevoidPreConfigureServices(ServiceConfigurationContextcontext){// 设置默认值,允许下游模块覆盖context.Services.AddSingleton(newDefaultOptions{...});}

验证结果

运行练习示例,输出清晰展示了顺序:

[LoggingModule] ConfigureServices ← 被依赖的模块先执行 [DataAccessModule] ConfigureServices [BusinessModule] ConfigureServices ← 入口模块最后执行

即使打乱[DependsOn]的书写顺序,输出顺序不变。原因就是SortByDependencies的拓扑排序保证了依赖顺序,和代码中的书写顺序无关。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询