夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node基础

  • 《MySQL》学习笔记

  • Midway

  • Nest

    • 开篇词
    • 学习理由
    • nest概念扫盲
    • 快速掌握 nestcli
    • 5种http数据传输方式
    • IoC 解决了什么痛点问题?
    • 如何调试 Nest 项目
    • Provider注入对象
    • 全局模块和生命周期
    • AOP 架构有什么好处?
    • 一网打尽 Nest 全部装饰器
    • Nest如何自定义装饰器
    • Metadata和Reflector
    • ExecutionContext切换上下文
    • Module和Provider的循环依赖处理
      • 两个 module 互相引用导致的问题
      • 解决办法:forwardRef
      • 类推 Service 之间的循环依赖
      • 示例
        • 1. 定义服务
        • 2. 配置模块
        • 3. 确保根模块正确导入
      • 总结
    • 如何创建动态模块
    • Nest和Express,fastify
    • Nest的Middleware
    • RxJS和Interceptor
    • 内置Pipe和自定义Pipe
    • ValidationPipe验证post请求参数
    • 如何自定义 Exception Filter
    • 图解串一串 Nest 核心概念
    • 接口如何实现多版本共存
    • Express如何使用multer实现文件上传
    • Nest使用multer实现文件上传
    • 图书管理系统
    • 大文件分片上传
    • 最完美的 OSS 上传方案
    • Nest里如何打印日志
    • 为什么Node里要用Winston打印日志
    • Nest 集成日志框架 Winston
    • 通过Desktop学Docker也太简单了
    • 你的第一个 Dockerfile
  • 其他

  • 服务端
  • Nest
神说要有光
2025-03-10
目录

Module和Provider的循环依赖处理

Nest 实现了一套模块系统,模块可以通过 imports 声明对其他模块的引用。

那 Module 和 Module 如果相互引用、形成循环依赖了怎么办?

这节我们就来学习下循环依赖的处理方式。

执行

nest new module-test -p npm
1

创建一个 nest 项目。

然后执行

nest g module aaa
nest g module bbb
1
2

创建两个 Module。

然后这两个 Module 相互引用。

这时候你执行

nest start -w
1

# 两个 module 互相引用导致的问题

把服务跑起来,会报这样的错误:

意思是在解析 BbbModule 的时候,它的第一个 imports 是 undefined。

这有两个原因,一个是这个值本来就是 undefined,第二个就是形成了循环依赖。

因为 Nest 创建 Module 的时候会递归创建它的依赖,而它的依赖又依赖了这个 Module,所以没法创建成功,拿到的就是 undefined。

那怎么办呢?

# 解决办法:forwardRef

其实我们可以先单独创建这两个 Module,然后再让两者关联起来。

也就是用 forwardRef 的方式:

因为我们用了 nest start --watch 的方式启动的,nest 会自动重启,这时候就没有错误了:

nest 会单独创建两个 Module,之后再把 Module 的引用转发过去,也就是 forwardRef 的含义。

# 类推 Service 之间的循环依赖

除了 Module 和 Module 之间会循环依赖以外,provider 之间也会。

比如 Service 里可以注入别的 Service,自身也可以用来注入。

所以也会有循环引用。

我们来测试下:

nest g service ccc --no-spec --flat
nest g service ddd --no-spec --flat
1
2

分别创建 ccc 和 ddd 两个 service,--no-spec 是不生成测试文件,--flat 是平铺。

就会创建这两个 service,并在 AppModule 引入了:

然后我们让两者相互注入:

import { Injectable } from '@nestjs/common';
import { CccService } from './ccc.service';

@Injectable()
export class DddService {
    constructor(private cccService: CccService) {}

    ddd() {
        return this.cccService.ccc()  + 'ddd';
    }
}
1
2
3
4
5
6
7
8
9
10
11
import { Injectable } from '@nestjs/common';
import { DddService } from './ddd.service';

@Injectable()
export class CccService {
    constructor(private dddService: DddService) {}

    ccc() {
        return 'ccc';
    }

    eee() {
        return this.dddService.ddd() + 'eee';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

两个 service 分别依赖了对方的方法。

在 AppService 里调用下:

import { Injectable } from '@nestjs/common';
import { CccService } from './ccc.service';
import { DddService } from './ddd.service';

@Injectable()
export class AppService {
  constructor(private cccService: CccService, private dddService: DddService){}

  getHello(): string {
    return this.dddService.ddd() + this.cccService.eee();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

这时候 nest start --watch 会报错:

说是没法解析 DddService 的依赖,也是因为循环依赖导致的。

这时候也是通过 forwardRef 解决:

这时候就不能用默认的注入方式了,通过 @Inject 手动指定注入的 token,这里是 forwardRef 的方式注入。

这样报错就没了:

浏览器访问下:

两个 service 的相互调用也成功了。

这样我们就解决了循环依赖的问题。

案例代码在小册仓库 (opens new window)。

# 示例

假如a模块中有aservice,b模块中有bservice,aservice中有个方法使用了bservice中的方法,而bservice中有个方法使用了aservice中的一个方法,产生了循环引用。

# 1. 定义服务

首先,确保你的服务定义中正确使用了 @Inject 和 forwardRef 来打破循环依赖。

a.service.ts

import { Injectable, Inject } from '@nestjs/common';
import { forwardRef } from '@nestjs/core';
import { BService } from '../b/b.service';

@Injectable()
export class AService {
  constructor(
    @Inject(forwardRef(() => BService))
    private readonly bService: BService,
  ) {}

  methodA() {
    console.log('Method A');
    return this.bService.methodB();
  }

  anotherMethodA() {
    console.log('Another Method A');
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

b.service.ts

import { Injectable, Inject } from '@nestjs/common';
import { forwardRef } from '@nestjs/core';
import { AService } from '../a/a.service';

@Injectable()
export class BService {
  constructor(
    @Inject(forwardRef(() => AService))
    private readonly aService: AService,
  ) {}

  methodB() {
    console.log('Method B');
    return this.aService.anotherMethodA();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2. 配置模块

接下来,在模块配置中也需要使用 forwardRef 来确保 NestJS 正确识别和处理这些循环依赖的服务。

a.module.ts

import { Module } from '@nestjs/common';
import { forwardRef } from '@nestjs/core';
import { AService } from './a.service';
import { BModule } from './b/b.module';

@Module({
  providers: [AService],
  imports: [forwardRef(() => BModule)], // 使用 forwardRef 引用 BModule
  exports: [AService],
})
export class AModule {}
1
2
3
4
5
6
7
8
9
10
11

b.module.ts

typescript

浅色版本

import { Module } from '@nestjs/common';
import { forwardRef } from '@nestjs/core';
import { BService } from './b.service';
import { AModule } from '../a/a.module';

@Module({
  providers: [BService],
  imports: [forwardRef(() => AModule)], // 使用 forwardRef 引用 AModule
  exports: [BService],
})
export class BModule {}
1
2
3
4
5
6
7
8
9
10
11

# 3. 确保根模块正确导入

确保你的根模块(通常是 AppModule)正确导入了这两个模块。注意,不需要在根模块中再次使用 forwardRef,因为它们已经在各自的模块中被正确引用了。

app.module.ts

typescript

浅色版本

import { Module } from '@nestjs/common';
import { AModule } from './a/a.module';
import { BModule } from './b/b.module';

@Module({
  imports: [AModule, BModule], // 直接导入两个模块
})
export class AppModule {}
1
2
3
4
5
6
7
8

# 总结

Module 之间可以相互 imports,Provider 之间可以相互注入,这两者都会形成循环依赖。

解决方式就是两边都用 forwardRef 来包裹下。

它的原理就是 nest 会先创建 Module、Provider,之后再把引用转发到对方,也就是 forward ref。

编辑 (opens new window)
上次更新: 2025/5/14 16:47:16
ExecutionContext切换上下文
如何创建动态模块

← ExecutionContext切换上下文 如何创建动态模块→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式