Skip to content

模块导出 provider,另一个模块需要 imports 它才能用这些 provider。

但如果这个模块被很多模块依赖了,那每次都要 imports 就很麻烦。

能不能设置成全局的,它导出的 provider 直接可用呢?

Module、Controller、Provider 是由 Nest 创建的,能不能在创建、销毁的时候执行一些逻辑呢?

这节我们来学习下全局模块和生命周期。

创建一个 nest 项目:

nest new global-and-lifecycle -p npm

然后创建两个 CRUD 的模块:

nest g resource aaa --no-spec
nest g resource bbb --no-spec

--no-spec 是不生成测试文件

在 AaaModule 里指定 exports 的 provider:

然后在 BbbModule 里 imports:

这样就可以在 BbbModule 内注入 AaaService 了:

把 nest 服务跑起来:

npm run start:dev

可以看到 aaaService 生效了:

这是我们常用的引入 Module 的方式。

但如果这个 AaaModule 被很多地方引用呢?

每个模块都 imports 太麻烦了,这时候就可以把它声明为全局的:

在 AaaModule 上加一个 @Global 的装饰器,然后在 BbbModule 里把 AaaModule 的 imports 去掉。

这样依然是可以注入的:

这就是全局模块。

不过全局模块还是尽量少用,不然注入的很多 provider 都不知道来源,会降低代码的可维护性。

然后是生命周期:

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

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

这个过程中,Nest 暴露了一些生命周期方法:

首先,递归初始化模块,会依次调用模块内的 controller、provider 的 onModuleInit 方法,然后再调用 module 的 onModuleInit 方法。

全部初始化完之后,再依次调用模块内的 controller、provider 的 onApplicationBootstrap 方法,然后调用 module 的 onApplicationBootstrap 方法

然后监听网络端口。

之后 Nest 应用就正常运行了。

这个过程中,onModuleInit、onApplicationBootstrap 都是我们可以实现的生命周期方法。

我们来试一下:

再创建两个 Module:

nest g resource ccc --no-spec
nest g resource ddd --no-spec

nest 提供了这样两个 interface:

在 controller、service、module 里分别实现它:

ddd 模块也是这样。

然后重新跑下服务,会看到这样的日志信息:

这就是 onModuleInit 和 onApplicationBootstrap 生命周期的调用顺序。

应用销毁的时候也同样有生命周期:

先调用每个模块的 controller、provider 的 onModuleDestroy 方法,然后调用 Module 的 onModuleDestroy 方法。

之后再调用每个模块的 controller、provider 的 beforeApplicationShutdown 方法,然后调用 Module 的 beforeApplicationShutdown 方法。

然后停止监听网络端口。

之后调用每个模块的 controller、provider 的 onApplicationShutdown 方法,然后调用 Module 的 onApplicationShutdown 方法。

之后停止进程。

是不是感觉 onModuleDestory 和 beforeApplicationShutdown 没区别呀?

其实是有区别的,可以看下对应的 interface:

beforeApplicationShutdown 是可以拿到 signal 系统信号的,比如 SIGTERM。

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

我们分别给 CccController、CccProvider、CccModule 还有 ddd 模块的那些给加一下:

3s 后调用 app.close() 触发销毁(app.close() 只是触发销毁逻辑,但不会真正退出进程)

生命周期方法是这样的执行顺序:

而且所有的生命周期函数都是支持 async 的。

我们来看看 @nestjs/typeorm、@nestjs/mongoose 里都是怎么用的:

可以看到,一般都是通过 moduleRef 取出一些 provider 来销毁,比如关闭连接。

这里的 moduleRef 就是当前模块的引用。

我们来试试:

onApplicationShutdown 的生命周期里,拿到当前模块的引用 moduleRef,调用 get 方法,传入 token,取出对应的 provider 实例,然后调用它的方法。

javascript
import { Module, OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown  } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { CccService } from './ccc.service';
import { CccController } from './ccc.controller';

@Module({
  controllers: [CccController],
  providers: [CccService]
})
export class CccModule implements OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown  {


  onModuleDestroy() {
    console.log('CccModule onModuleDestroy');
  }

  beforeApplicationShutdown(signal: string) {
    console.log('CccModule beforeApplicationShutdown', signal);
  }

  onApplicationShutdown() {
    const cccService = this.moduleRef.get<CccService>(CccService);
    console.log('--------------------------', cccService.findAll());

    console.log('CccModule onApplicationShutdown');
  }

  onModuleInit() {
    console.log('CccModule OnModuleInit');
  }

  onApplicationBootstrap() {
    console.log('CccModule onApplicationBootstrap');
  }
}

这就是 onApplicationShutdown 生命周期的常见用法。

案例代码在小册仓库

总结

这节我们学习了全局模块和生命周期。

模块可以通过 @Global 声明为全局的,这样它 exports 的 provider 就可以在各处使用了,不需要 imports。

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

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

全局模块、生命周期、moduleRef 都是 Nest 很常用的功能。