全局模块和生命周期

全局模块

如果一个模块被很多个模块依赖了,那么每次 import 就会很麻烦。

最好的方法是使用@Global装饰器设置成全局模块。

ts
1// 当前 Module 属于 AppModule 模块
2@Global()
3@Module({
4  controllers: [AppController, UserController],
5  providers: [AppService],
6})
7export class AppModule {}

这样在其他模块下可以直接注入,不需要再 import。

ts
1// 当前 Controller 属于 User 模块
2@Controller('/api/user')
3export class UserController {
4  constructor(
5    @Inject(AppService)
6    private readonly appService: AppService,
7  ) {}
8
9  @Get()
10  getService() {
11    return this.appService.getHello()
12  }
13}

生命周期方法

Nest 在启动的时候,会递归解析 Module 依赖,扫描其中的 provider、controller,注入它的依赖。

全部解析完后,会监听网络端口,开始处理请求。

在这个过程中,暴露了一些 API,根据顺序我们称之为生命周期方法:

img

当应用启动时:

  • 首先,递归初始化模块,依次调用模块内的 Controller、provider 的onModuleInit,再调用模块的 onModuleInit 方法。
  • 全部初始化完之后,再依次调用模块内的 controller、provider 的 onApplicationBootstrap 方法,然后调用 module 的 onApplicationBootstrap 方法
  • 然后是监听端口。
  • 最后 Nest 应用运行

我们来测试一下:

bash
1nest generate resource user

然后在 UserModule 实现onModuleInitonApplicationBootstrap方法:

ts
1export class UserModule implements OnApplicationBootstrap, OnModuleInit {
2  onModuleInit() {
3    console.log('user module init')
4  }
5  onApplicationBootstrap() {
6    console.log('user module bootstrap')
7  }
8}

并在 UserController 实现这两个方法:

ts
1@Controller('user')
2export class UserController implements OnApplicationBootstrap, OnModuleInit {
3  constructor(private readonly userService: UserService) {}
4  onApplicationBootstrap() {
5    console.log('user controller bootstrap')
6  }
7  onModuleInit() {
8    console.log('user controller init')
9  }
10}

最后在 AppModule 处实现这两个方法:

ts
1export class AppModule implements OnApplicationBootstrap, OnModuleInit {
2  onModuleInit() {
3    console.log('app module init')
4  }
5  onApplicationBootstrap() {
6    console.log('app module bootstrap')
7  }
8}

最后启动应用,打开终端,会看到以下日志:

bash
1user controller init
2user module init
3app module init
4user controller bootstrap
5user module bootstrap
6app module bootstrap

可以看到如官方所述,从UserModuleAppModule,从controllermodule, Nest 确实是自下而上按照顺序递归执行这两个生命周期方法。

当应用销毁时:

  • 先调用每个模块的 controller、provider 的 onModuleDestroy 方法,然后调用 Module 的 onModuleDestroy 方法。
  • 之后再调用每个模块的 controller、provider 的 beforeApplicationShutdown 方法,然后调用 Module 的 beforeApplicationShutdown 方法。
  • 然后停止监听网络端口。
  • 之后调用每个模块的 controller、provider 的 onApplicationShutdown 方法,然后调用 Module 的 onApplicationShutdown 方法。
  • 停止进程

beforeApplicationShutdownonModuleDestroy有一个区别,我们来查看 nest 对他们的接口声明:

ts
1export interface OnModuleDestroy {
2  onModuleDestroy(): any
3}

beforeApplicationShutdown接受一个 signal。

ts
1export interface BeforeApplicationShutdown {
2  beforeApplicationShutdown(signal?: string): any
3}

这说明 beforeApplicationShutdown 方法可以接受 signal 系统信号,比如 SIGTERM。

这些终止信号是别的进程传过来的,让它做一些销毁的事情,比如用 k8s 管理容器的时候,可以通过这个信号来通知它。

现在我们在 AppModule 里实现一下他们:

ts
1@Module({
2  controllers: [AppController],
3  providers: [AppService],
4  imports: [UserModule],
5})
6export class AppModule implements BeforeApplicationShutdown, OnApplicationShutdown, OnModuleDestroy {
7  beforeApplicationShutdown(signal?: string) {
8    console.log('——————🚀🚀🚀🚀🚀 —— beforeApplicationShutdown:', signal)
9  }
10  onApplicationShutdown(signal?: string) {
11    console.log('——————🚀🚀🚀🚀🚀 —— onApplicationShutdown:', signal)
12  }
13  onModuleDestroy() {
14    console.log('app module destroy')
15  }
16}

接着在main.tsbootstrap方法调用 app.close 触发销毁:

ts
1async function bootstrap() {
2  const app = await NestFactory.create(AppModule)
3  await app.listen(3000)
4  setTimeout(() => {
5    app.close()
6  }, 3000)
7}
8bootstrap()

三秒钟后,再看 log 信息:

bash
1app module destroy
2——————🚀🚀🚀🚀🚀 —— beforeApplicationShutdown: undefined
3——————🚀🚀🚀🚀🚀 —— onApplicationShutdown: undefined

moduleRef

如果想要生命周期函数内,拿到模块对象里的内容(例如 provider),可以使用 moduleRef。

这里的 moduleRef 就是模块的引用对象。

image-20231002153020002

在 onApplicationShutdown 生命周期里,拿到 ModuleRef (模块的引用),调用 get 方法传入 token,获取对应的实例,然后调用它的方法拿到结果。

Log 信息:

bash
1——————🚀🚀🚀🚀🚀 —— onApplicationShutdown: Hello World!

总结

如果有一个模块被多个模块所引用,一个比较好的方法是用 @Global 将其变成全局模块。

provider、controller、module 都支持启动和销毁的生命周期函数,这些生命周期函数都支持 async 的方式。

可以在其中做一些初始化、销毁的逻辑,比如 onApplicationShutwon 里通过 moduleRef.get 取出一些 provider,执行关闭连接等销毁逻辑。