十、模块的导入

前边我们说了ControllerProvider,都是在appModule直接引入的,这就是我们的根模块,是构建应用图的起点。我们还会有其他的各种模块,每个模块封装了一组密切相关的功能。

@Module装饰器接收一个对象,对象的属性描述了模块:

  • providers - 由Nest注入容器实例化的提供者,可以在当前模块内共享
  • controllers - 此模块定义的一组控制器
  • imports - 导入的模块列表,被导入的模块中导出当前模块中所需要的providers
  • exports - 导出的providers, 是当前模块的providers的子集,可以在其他导入当前模块的模块中使用(也可以重新导出导入的模块,这种情况下就不是providers的子集了)

关于providerscontrollers,我们前边已经讲过了,而importsexports就是与模块相关的内容了。

本章节我们关注模块的导入,也就是imports的内容

前置准备

我们之前开发了user.serviceuser.controller,现在我们新建一个user.module文件,将user功能封装到一个模块中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// user.module.ts
import { Module } from "./@nestjs/common";
import { UseValueService } from "./use-value.service";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";

@Module({
controllers: [UserController],
providers: [
UserService,
{
provide: 'StringToken', // 这是一个标志,也就是provider的名称,用来拿到对应值
useValue: new UseValueService() // useValue直接提供一个值
},
],
exports: [
UserService,
'StringToken'
]
})
export class UserModule {}

app.module中,我们也进行调整,只要导入user.module就可以了

1
2
3
4
5
6
7
8
9
10
11
// app.module.ts
import { Module } from './@nestjs/common';
import { AppController } from './app.controller';
import { UserModule } from './user.module';
@Module({
controllers: [AppController],
providers: [],
imports: [UserModule]
})
export class AppModule {}

接下来我们要开始源码的部分了。

源码实现

首先完善一下@Module装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// @nestjs/common/module.decorator.ts
import 'reflect-metadata'

interface ModuleMetadata {
// 控制器
controllers?: Function[]
// providers
providers?: any[]
// 导入其他模块,可以使用导入模块中导出的providers
imports?: any[]
// 导出部分providers,供其他导入该模块的模块使用
exports?: any[]
}

// Module装饰器工厂函数
export function Module(metadata: ModuleMetadata): ClassDecorator {
return (target: Function) => {
// 给类添加元数据controllers,值为传入Module的controllers
Reflect.defineMetadata('controllers', metadata.controllers, target)
// 给类添加元数据providers,值为要注入容器的所有provider
Reflect.defineMetadata('providers', metadata.providers, target)
// 给类添加元数据imports,值为传入Module的imports
Reflect.defineMetadata('imports', metadata.imports, target)
// 给类添加元数据exports,值为要注入容器的所有exports
Reflect.defineMetadata('exports', metadata.exports, target)
}
}

之前我们在nest-application中,调用initProviders注册了所有的提供者,现在引入了模块的概念,要复杂的多,所以我们重写initProviders的流程,并添加了一个addProvider方法实现原有的注册功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// @nestjs/core/nest-application.ts
initProviders() {
// 拿到导入的模块
const imports = Reflect.getMetadata('imports', this.module) ?? []
// 遍历所有导入的模块
for (const importModule of imports) {
// 拿到导入模块的providers
const importedProviders = Reflect.getMetadata('providers', importModule) ?? []
// 把provider保存起来
for (const provider of importedProviders) {
this.addProvider(provider)
}
}
// 当前模块的providers也需要保存
const providers = Reflect.getMetadata('providers', this.module) ?? []
for (const provider of providers) {
this.addProvider(provider)
}
}
addProvider(provider) {
// 如果已经有了就直接返回,避免循环依赖
const injectToken = provider?.provide ?? provider
if (this.providers.has(injectToken)) {
return
}
if (provider.provide && provider.useClass) {
// 提供的是一个类
// 依赖项可能有其他依赖,需要递归instance的依赖
const dependencies = this.resolveDependencies(provider.useClass)
// 创建实例
const instance = new provider.useClass(...dependencies)
// 保存到providers
this.providers.set(provider.provide, instance)
} else if (provider.provide && provider.useValue) {
// 提供的是一个值
this.providers.set(provider.provide, provider.useValue)
} else if (provider.provide && provider.useFactory) {
// 提供的是一个函数,我们保存函数执行结果
// 拿到要注入的参数,有可能是一个其他的token
const inject = provider.inject ?? [].map(token => {
// 找到对应token则返回对应值,否则是一个常量,直接返回
return this.providers.get(token) ?? token
})

// 注入参数并保存
this.providers.set(provider.provide, provider.useFactory(...inject))
} else {
// 直接传的类名
// 递归依赖
const dependencies = this.resolveDependencies(provider)
this.providers.set(provider, new provider(...dependencies))
}
}

现在只是以AppModule为根模块,将AppModule以及所有导入模块的provider注册了,功能实现并不完全,比如:

  • 导入的模块可能会导入其他模块,没有实现递归的模块依赖关系分析
  • 导入的模块的providers可能只导出了一部分,没有全量导出,但是我们进行的全量注册
  • 只解析了根模块的controller,其他模块注册的路由暂无法使用
  • provider没有实现模块间的隔离
  • exports还没有解析

本节我们只实现导入功能,这里说的这些功能等到后边再来实现

作者

胡兆磊

发布于

2025-05-06

更新于

2025-05-06

许可协议