从入口看起:AbpApplicationFactory.Create
usingvarapp=AbpApplicationFactory.Create<BusinessModule>(options=>{options.UseAutofac();});app.Initialize();这行代码的背后发生了什么?直接看源码AbpApplicationFactory.cs:
publicstaticIAbpApplicationWithInternalServiceProviderCreate<TStartupModule>(Action<AbpApplicationCreationOptions>?optionsAction=null){returnnewAbpApplicationWithInternalServiceProvider(typeof(TStartupModule),optionsAction);}它创建了AbpApplicationWithInternalServiceProvider,这个类在构造时做了两件关键的事:
- 创建
ModuleLoader并调用LoadModules加载所有模块 - 按拓扑排序确定模块初始化顺序
深度解析 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的拓扑排序保证了依赖顺序,和代码中的书写顺序无关。