Nest 实现了一套模块系统,模块可以通过 imports 声明对其他模块的引用。
那 Module 和 Module 如果相互引用、形成循环依赖了怎么办?
这节我们就来学习下循环依赖的处理方式。
执行
nest new module-test -p npm
创建一个 nest 项目。
然后执行
nest g module aaa
nest g module bbb
2
创建两个 Module。
然后这两个 Module 相互引用。
这时候你执行
nest start -w
# 两个 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
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';
}
}
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';
}
}
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();
}
}
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');
}
}
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();
}
}
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 {}
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 {}
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 {}
2
3
4
5
6
7
8
# 总结
Module 之间可以相互 imports,Provider 之间可以相互注入,这两者都会形成循环依赖。
解决方式就是两边都用 forwardRef 来包裹下。
它的原理就是 nest 会先创建 Module、Provider,之后再把引用转发到对方,也就是 forward ref。