You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何我的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的模块默认是单例的,这种情况少见)。

正确的解决方案

要实现服务的单例共享,正确的做法是:

  1. 创建专门的NgrokModule,在该模块的providers中声明NgrokService,并通过exports导出它:
@Module({
  providers: [NgrokService],
  exports: [NgrokService],
})
export class NgrokModule {}
  1. 在需要使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 23:01:29