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

如何定位NestJS提示的具体循环依赖来源?

如何定位NestJS中未明确提示的循环依赖?

问题背景

我创建了一个用于操作Redis的全局模块,最初仅提供Redis客户端,运行正常可在任意位置注入:

// redis.module.ts
import { Global, Module } from '@nestjs/common'
import { createClient } from 'redis'
import { RedisMutex } from './redis-mutex.service'

export const REDIS = 'redis'

@Global()
@Module({
  providers: [
    {
      provide: REDIS,
      useFactory: async () => {
        const client = createClient()
        await client.connect()
        return client
      },
    },
  ],
  exports: [REDIS],
})
export class RedisModule {}

随后在同一模块中实现了基于Redis客户端的互斥锁服务RedisMutex:

// redis-mutex.service.ts
import { Inject, Injectable } from '@nestjs/common'
import { randomBytes } from 'crypto'
import { RedisClientType } from 'redis'
import { REDIS } from './redis.module'

@Injectable()
export class RedisMutex {
  constructor(@Inject(REDIS) private redis: RedisClientType) {}

  async lock(lockName: string): Promise<() => void> {
    // ...业务代码
  }
}

将RedisMutex添加为模块提供者并导出后,应用仍正常运行:

// 修改后的redis.module.ts
import { Global, Module } from '@nestjs/common'
import { createClient } from 'redis'
import { RedisMutex } from './redis-mutex.service'

export const REDIS = 'redis'

@Global()
@Module({
  providers: [
    {
      provide: REDIS,
      useFactory: async () => {
        const client = createClient()
        await client.connect()
        return client
      },
    },
    RedisMutex,
  ],
  exports: [REDIS, RedisMutex],
})
export class RedisModule {}

但当我将RedisMutex注入到外部模块的两个提供者中(这些模块未显式导入RedisModule,仅注入RedisMutex,RedisModule仅在AppModule的imports中)后,NestJS提示循环依赖,但未给出具体依赖链:

Error: A circular dependency has been detected. Please, make sure that each side of a bidirectional relationships are decorated with "forwardRef()".
    at NestContainer.addProvider (/home/andrey/Code/cashflowio/node_modules/@nestjs/core/injector/container.js:107:19)
    at DependenciesScanner.insertProvider (/home/andrey/Code/cashflowio/node_modules/@nestjs/core/scanner.js:201:35)
    at /home/andrey/Code/cashflowio/node_modules/@nestjs/core/scanner.js:105:18
    at Array.forEach (<anonymous>)
    at DependenciesScanner.reflectProviders (/home/andrey/Code/cashflowio/node_modules/@nestjs/core/scanner.js:104:19)
    at DependenciesScanner.scanModulesForDependencies (/home/andrey/Code/cashflowio/node_modules/@nestjs/core/scanner.js:85:18)
    at DependenciesScanner.scan (/home/andrey/Code/cashflowio/node_modules/@nestjs/core/scanner.js:29:9)
    at /home/andrey/Code/cashflowio/node_modules/@nestjs/core/nest-factory.js:95:17
    at Function.asyncRun (/home/andrey/Code/cashflowio/node_modules/@nestjs/core/errors/exceptions-zone.js:22:13)
    at NestFactoryStatic.initialize (/home/andrey/Code/cashflowio/node_modules/@nestjs/core/nest-factory.js:94:13)

尝试为所有新注入使用forwardRef()但无效,需要定位具体的循环依赖来源。

定位方法

1. 启用Verbose级别的调试日志

修改应用启动代码,开启更详细的日志输出,能看到NestJS扫描依赖的全过程:

const app = await NestFactory.create(AppModule, {
  logger: ['log', 'debug', 'verbose']
});

日志会打印正在处理的模块和提供者,循环依赖触发时的上下文信息能帮你锁定问题点。

2. 逐步排查提供者注释法

临时注释掉其中一个外部模块对RedisMutex的注入,重新启动应用:

  • 如果错误消失,说明问题出在该提供者与其他服务的依赖关系中;
  • 如果错误仍存在,再排查另一个提供者。
    通过这种方式逐步缩小范围,找到触发循环的两个关联对象。

3. 检查间接依赖链

即使外部模块没有显式导入RedisModule,也要检查这些模块的提供者是否存在相互依赖:
比如Provider A注入了RedisMutex,同时Provider A被Provider B依赖,而Provider B又间接依赖了某个依赖RedisMutex的服务,形成闭环。

4. 打印完整依赖树

通过NestJS的容器对象打印所有模块和提供者的依赖关系,手动排查循环链条:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { logger: false });
  const container = app.get('NestContainer');
  
  const modules = container.getModules();
  modules.forEach((module, moduleKey) => {
    console.log(`=== 模块: ${moduleKey} ===`);
    const providers = module.providers;
    providers.forEach((provider, providerKey) => {
      console.log(`  提供者: ${providerKey}`);
      console.log(`    依赖项: ${provider.dependencies?.map(dep => dep.toString()).join(', ')}`);
    });
  });
  
  await app.close();
}
bootstrap();

运行这段代码后,遍历输出的依赖关系,寻找A依赖B、B又依赖A的闭环。

5. 检查RedisMutex内部的隐含依赖

仔细检查RedisMutex的lock方法实现,是否在内部调用了某个依赖外部模块服务的逻辑?比如如果lock方法中用到了某个注入的服务,而该服务又依赖了注入RedisMutex的提供者,就会形成隐蔽的循环依赖。

内容的提问来源于stack exchange,提问作者JaffParker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 07:45:38