如何简便从NestJS DI容器获取指定基类的所有子类实例
NestJS获取所有基类子类实例并实现并行优雅关停方案
核心实现思路
不依赖NestJS内置串行执行的生命周期钩子,通过两种方式从DI容器拿到所有继承BasePoller的实例,在收到SIGTERM/SIGINT信号时用Promise.all并行触发所有实例的关停逻辑,大幅缩短多轮询器场景下的关停耗时。
方案1:装饰器自动收集(推荐,无扫描开销)
通过自定义装饰器给所有轮询器子类绑定统一的多提供者注入令牌,启动时直接注入即可拿到全量实例,零运行时扫描成本。
- 首先定义基类、装饰器和注入令牌:
import { SetMetadata, Injectable, Inject } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; export const POLLER_INSTANCES = 'POLLER_INSTANCES'; const IS_POLLER_META_KEY = 'is_poller_implementation'; // 轮询器类装饰器,所有继承BasePoller的服务需加该装饰器 export function Poller() { return (target: any) => { SetMetadata(IS_POLLER_META_KEY, true)(target); Reflect.defineMetadata( 'poller_self_provider', { provide: POLLER_INSTANCES, useExisting: target, multi: true, }, target ); }; } // 轮询器基类 @Injectable() export abstract class BasePoller { abstract start(): Promise<void>; abstract stop(): Promise<void>; }
- 所有轮询器实现类加
@Poller()装饰器:
@Injectable() @Poller() export class OrderStatusPoller extends BasePoller { // 实现start、stop逻辑 }
- 在根模块中注入实例、绑定信号监听:
import { Module, OnApplicationBootstrap } from '@nestjs/common'; @Module({ // 导入业务模块、注册所有轮询器服务 }) export class AppModule implements OnApplicationBootstrap { constructor( @Inject(POLLER_INSTANCES) private readonly pollers: BasePoller[], private readonly moduleRef: ModuleRef ) {} onApplicationBootstrap() { // 应用启动完成后绑定系统信号 process.on('SIGTERM', () => this.parallelShutdown()); process.on('SIGINT', () => this.parallelShutdown()); } private async parallelShutdown() { console.log(`Received shutdown signal, stopping ${this.pollers.length} pollers in parallel`); // 并行执行所有轮询器关停逻辑,单个失败不阻塞整体流程 await Promise.all( this.pollers.map(poller => poller.stop().catch(err => console.error('Poller stop failed:', err)) ) ); // 所有轮询器停完再关闭整个Nest应用 await this.moduleRef.close(); process.exit(0); } }
方案2:DiscoveryService动态扫描(零侵入,无需装饰器)
如果不想给每个轮询器类单独加装饰器,可以用Nest内置的DiscoveryService扫描DI容器中所有已实例化的提供者,过滤出继承自BasePoller的实例。
- 根模块导入
DiscoveryModule:
import { Module, OnApplicationBootstrap, DiscoveryModule } from '@nestjs/core'; @Module({ imports: [DiscoveryModule], // 其他模块、服务注册 })
- 启动阶段扫描获取轮询器实例:
import { DiscoveryService } from '@nestjs/core'; export class AppModule implements OnApplicationBootstrap { private pollers: BasePoller[] = []; constructor( private readonly discoveryService: DiscoveryService, private readonly moduleRef: ModuleRef ) {} onApplicationBootstrap() { // 过滤所有DI容器中的提供者,拿到BasePoller子类实例 this.pollers = this.discoveryService.getProviders() .filter(wrapper => { if (!wrapper.instance || !wrapper.metatype) return false; return wrapper.metatype.prototype instanceof BasePoller; }) .map(wrapper => wrapper.instance as BasePoller); // 信号绑定、并行关停逻辑和方案1完全一致 process.on('SIGTERM', () => this.parallelShutdown()); process.on('SIGINT', () => this.parallelShutdown()); } // 复用方案1中的parallelShutdown方法即可 }
优化建议
- 给单个轮询器的关停逻辑加超时控制,避免单个轮询器卡死导致整个关停流程挂起:
const withTimeout = (task: Promise<void>, timeout = 5000) => Promise.race([task, new Promise(resolve => setTimeout(resolve, timeout))]); // 调用时替换为 await Promise.all( this.pollers.map(poller => withTimeout(poller.stop().catch(err => console.error('Poller stop failed:', err))) ) ); - 不要在Nest内置的
OnModuleDestroy/OnApplicationShutdown生命周期中写轮询器关停逻辑,这两个钩子会按模块加载顺序串行执行,轮询器数量越多关停越慢。 - 必须等所有轮询器关停完成后再调用
moduleRef.close(),避免数据库连接、HTTP服务提前断开引发业务报错。
内容的提问来源于stack exchange,提问作者Max Heinritz
相关产品推荐
相关产品推荐

