NestJS 生命周期钩子


NestJS 生命周期钩子

大家好,今天我们来聊一聊 NestJS 框架中一个非常实用但可能被忽视的概念——生命周期钩子。如果你曾经在应用启动时需要做一些初始化工作(比如连接数据库),或者在应用关闭时需要释放资源(比如关闭数据库连接),那么生命周期钩子就是你的救星。它让我们能够在应用的不同阶段插入自定义逻辑,就像是给应用装上了“生命探测器”,随时感知它的状态变化。

什么是生命周期钩子?

简单来说,生命周期钩子就是一些特殊的接口方法,它们会在 NestJS 应用启动和关闭的特定时刻被自动调用。你可以把这些钩子理解为应用的“出生证明”和“遗愿清单”——在应用诞生时执行一些操作,在应用即将消亡时做一些善后工作。

NestJS 为应用提供了两大类生命周期钩子:应用创建时的钩子和应用销毁时的钩子。下面我们分别来看。


一、应用创建时的钩子

当一个 NestJS 应用启动时,它会经历一个复杂的初始化过程:递归解析模块依赖、实例化各个提供者(provider)、控制器(controller)等。在这个过程中,有两个关键的生命周期钩子会被触发:

1. onModuleInit

这个钩子在模块初始化阶段被调用。具体来说,当 Nest 递归初始化每个模块时,它会先执行该模块内所有控制器和提供者onModuleInit 方法,然后再执行模块本身onModuleInit 方法。

你可以把它想象成一个“班级点名”的过程:老师(模块)先让每个学生(控制器/提供者)举手(执行自己的 onModuleInit),然后老师再总结(执行模块的 onModuleInit)。

使用场景:比如你需要从配置文件中加载一些数据,或者初始化缓存,都可以放在这里做。

2. onApplicationBootstrap

这个钩子在所有模块都初始化完成之后,但在应用开始监听网络端口之前被调用。执行顺序依然是:先执行每个模块内控制器和提供者的 onApplicationBootstrap,再执行模块本身的。

此时,应用的所有依赖都已经准备就绪,你可以安全地执行一些需要全局资源的操作,比如启动后台任务、预热数据等。

触发时机:应用初始化完成 → onApplicationBootstrap → 监听端口 → 正常运行。


二、应用销毁时的钩子

当你的应用需要停止时(比如收到系统关闭信号、手动调用 app.close()),NestJS 也提供了一系列钩子让你优雅地释放资源。销毁过程同样有清晰的顺序。

1. onModuleDestroy

当应用开始销毁流程时,首先触发的是 onModuleDestroy。顺序依然是:先调用每个模块内控制器和提供者的 onModuleDestroy,再调用模块本身的。

这个阶段通常用来做一些轻量级的清理工作,比如清除临时变量、停止某些内部任务。

2. beforeApplicationShutdown

接下来是 beforeApplicationShutdown。它会在 onModuleDestroy 之后、停止网络端口之前被调用。执行顺序同上:先控制器/提供者,后模块。

这个钩子有一个可选参数 signal?: string,可以用来接收外部传入的关闭信号(比如来自 Kubernetes 的 SIGTERM)。你可以根据不同的信号执行不同的销毁逻辑。

3. 停止监听网络端口

beforeApplicationShutdown 执行完毕后,Nest 会停止接收新的网络请求(即关闭 HTTP 服务器等)。

4. onApplicationShutdown

最后一步是 onApplicationShutdown。此时网络端口已经关闭,应用不再接受新请求,你可以在这里进行最终的资源释放,比如关闭数据库连接、断开消息队列等。

同样,它也有一个 signal 参数,并且你可以通过 moduleRef.get() 方法获取到需要的提供者实例,然后执行关闭逻辑。

注意onApplicationShutdown 是在所有模块的控制器/提供者执行完后,最后执行模块本身的。


三、执行顺序总结

为了方便记忆,我们用一张图来概括整个生命周期:

应用启动:
  └─ 递归初始化模块
       ├─ 控制器/提供者的 onModuleInit
       └─ 模块的 onModuleInit
  └─ 所有模块初始化完成
       ├─ 控制器/提供者的 onApplicationBootstrap
       └─ 模块的 onApplicationBootstrap
  └─ 监听网络端口
  └─ 应用正常运行

应用关闭:
  └─ 收到关闭信号
       ├─ 控制器/提供者的 onModuleDestroy
       └─ 模块的 onModuleDestroy
       ├─ 控制器/提供者的 beforeApplicationShutdown
       └─ 模块的 beforeApplicationShutdown
  └─ 停止监听网络端口
       ├─ 控制器/提供者的 onApplicationShutdown
       └─ 模块的 onApplicationShutdown
  └─ 进程退出

注意:每一个生命周期钩子都支持异步操作,你可以返回一个 Promise 或使用 async/await,Nest 会等待它们完成后再继续下一步。这确保了你在初始化或销毁过程中不会因为异步操作而中断。


四、实际应用场景举例

场景1:启动时连接数据库

假设你使用 TypeORM,并且希望在应用启动时就建立数据库连接,而不是等到第一个请求时才连接。你可以在某个 servicemodule 中实现 onModuleInit

@Injectable()
export class DatabaseService implements OnModuleInit {
  async onModuleInit() {
    await this.connectToDatabase();
    console.log('数据库连接成功');
  }
}

场景2:优雅关闭数据库连接

当应用关闭时,你需要确保所有数据库连接都被正确释放,避免连接泄漏:

@Injectable()
export class DatabaseService implements OnApplicationShutdown {
  async onApplicationShutdown(signal?: string) {
    await this.closeDatabaseConnection();
    console.log(`收到信号 ${signal},数据库连接已关闭`);
  }
}

场景3:Kubernetes 环境下的平滑下线

在 K8s 中,Pod 停止时会发送 SIGTERM 信号。你可以利用 beforeApplicationShutdownonApplicationShutdownsignal 参数来区分不同信号,执行不同的逻辑。


五、注意事项

  • 生命周期钩子需要在你的类中实现对应的接口(如 OnModuleInitOnApplicationBootstrap 等),并确保该类被 Nest 的 IoC 容器管理(即添加了 @Injectable() 装饰器)。
  • 如果多个提供者都实现了同一个钩子,它们的执行顺序取决于模块中 providers 数组的声明顺序。
  • 不要在这些钩子中执行耗时过长的操作,否则会阻塞应用启动或关闭。如果确实需要耗时操作,考虑使用异步方式并合理设置超时。

声明:麋鹿与鲸鱼|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - NestJS 生命周期钩子


Carpe Diem and Do what I like