为何我的NestJS Service会被实例化两次?
问题背景
我有如下的NgrokService:
@Injectable() export class NgrokService implements OnApplicationBootstrap, OnApplicationShutdown { port: string ngrokToken: string ngrok: typeof import('ngrok') | null = null tunnelUrl?: string constructor(private readonly configService: ConfigService) { this.port = this.configService.get('API_PORT') ?? '3001' this.ngrokToken = this.configService.getOrThrow('NGROK_TOKEN') if (process.env.NODE_ENV === 'development') this.ngrok = require('ngrok') } async onApplicationBootstrap() { if (process.env.NODE_ENV !== 'development') { return } this.tunnelUrl = await this.ngrok!.connect({ addr: `https://localhost:${this.port}`, subdomain: '<my_subdomain>', region: 'us', authtoken: this.ngrokToken, }) } async onApplicationShutdown() { if (process.env.NODE_ENV !== 'development') { return } this.ngrok!.disconnect() } }
我在HealthModule中引入该服务:
@Module({ imports: [], controllers: [HealthController], providers: [HealthService, AwsService, NgrokService], }) export class HealthModule {}
同时也在AppModule中引入它:
@Module({ imports: [ ... ], controllers: [...], providers: [ ... NgrokService ], }) export class AppModule {}
现在遇到的问题是onApplicationBootstrap钩子被调用了两次。我尝试将服务封装到模块中,并通过全局锁变量检查连接是否已建立来解决,但想知道:为什么NestJS会把这个服务实例化两次?即使封装到模块里,服务还是会被实例化两次。
原因解析
这是NestJS模块提供者的作用域机制导致的:
- NestJS默认情况下,每个模块的
providers数组中声明的服务,都会为该模块创建一个独立的实例。也就是说,当你在AppModule和HealthModule的providers里都添加了NgrokService时,这两个模块会各自初始化一个NgrokService实例,自然会触发两次onApplicationBootstrap钩子。 - 如果你尝试封装模块但问题依然存在,大概率是封装方式不正确:比如你只是把服务放到了某个模块的
providers里,但其他模块依然在自己的providers中重复声明了NgrokService;或者你创建的模块被多个地方重复导入且没有正确处理单例逻辑(不过NestJS的模块默认是单例的,这种情况少见)。
正确的解决方案
要实现服务的单例共享,正确的做法是:
- 创建专门的
NgrokModule,在该模块的providers中声明NgrokService,并通过exports导出它:
@Module({ providers: [NgrokService], exports: [NgrokService], }) export class NgrokModule {}
- 在需要使用
NgrokService的模块(比如AppModule和HealthModule)中,只需要导入NgrokModule,不要在自己的providers里再声明NgrokService:
// AppModule示例 @Module({ imports: [NgrokModule, ...], controllers: [...], providers: [...] // 移除NgrokService }) export class AppModule {} // HealthModule示例 @Module({ imports: [NgrokModule], controllers: [HealthController], providers: [HealthService, AwsService] // 移除NgrokService }) export class HealthModule {}
这样NestJS会在整个应用中维护NgrokService的唯一实例,钩子也就只会触发一次。
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

