Skip to content

生命周期事件

好的,我们来探索 NestJS 世界中一个非常重要的机制,它能让你精确地控制应用在启动和关闭过程中的行为。这就是生命周期事件 (Lifecycle Events)

掌控应用的“脉搏”:NestJS 生命周期事件全解析

想象一下发射一枚精密的火箭。

整个过程必须遵循一个严格的、预先设定的顺序

  1. 发射前自检:每个独立的部件(燃料泵、导航系统)都要先完成内部的初始化和检查。
  2. 主引擎点火:在所有部件都报告“正常”后,总控中心下达指令,点燃主引擎,准备升空。
  3. 任务执行:火箭在太空中平稳运行。
  4. 任务结束 & 回收:在任务完成后,需要执行一系列关闭程序,比如分离推进器、关闭引擎、释放降落伞等,以确保安全回收。

一个健壮的 NestJS 应用,其生命周期也像这次火箭发射一样,拥有一套清晰、可预测的事件序列。生命周期事件就是 NestJS 暴露给你的“钩子”(Hooks),让你可以在这些关键的“时间点”上,植入你自己的代码,执行特定的任务。

1. “发射序列”:应用启动时的生命周期

当你的 NestJS 应用启动时,主要会经历两个重要的生命周期事件。

OnModuleInit - “各模块自检”

  • 它是什么? 这是一个接口,当一个服务所在的模块(Host Module)完成了所有依赖的解析和注入后,这个钩子就会被调用。
  • 触发时机: 在模块内部。每个模块都有自己的 OnModuleInit 阶段。
  • 用途: 非常适合执行那些依赖于本模块其他服务的初始化逻辑。比如,一个 CacheService 需要在启动后,立即从 DatabaseService 获取一些初始数据填充到缓存中。

代码示例:CacheService 初始化

假设我们有一个 UsersModule,里面有 DbServiceUsersService。我们希望 UsersService 在一启动时就从数据库加载管理员列表。

src/users/db.service.ts

typescript
@Injectable()
export class DbService {
  getAdminUsers(): Promise<string[]> {
    console.log('[DbService] Fetching admin users from database...');
    return Promise.resolve(['Admin_John', 'Admin_Jane']);
  }
}

src/users/users.service.ts

typescript
// 1. 导入 OnModuleInit 接口
import { Injectable, OnModuleInit } from '@nestjs/common';
import { DbService } from './db.service';

// 2. 实现这个接口
@Injectable()
export class UsersService implements OnModuleInit {
  private adminUsers: string[] = [];

  constructor(private readonly dbService: DbService) {
    console.log(
      '[UsersService] Constructor called. Admin users are not loaded yet.'
    );
  }

  // 3. 实现 onModuleInit 方法
  async onModuleInit() {
    console.log(
      '[UsersService] onModuleInit hook is called. Time to load admins!'
    );
    this.adminUsers = await this.dbService.getAdminUsers();
    console.log(`[UsersService] Admins loaded: ${this.adminUsers.join(', ')}`);
  }

  getAdmins() {
    return this.adminUsers;
  }
}

控制台输出顺序:

[UsersService] Constructor called. Admin users are not loaded yet.
[UsersService] onModuleInit hook is called. Time to load admins!
[DbService] Fetching admin users from database...
[UsersService] Admins loaded: Admin_John, Admin_Jane

这证明了 onModuleInit 在构造函数之后、服务可供使用之前,完美地执行了我们的初始化逻辑。


OnApplicationBootstrap - “主引擎点火”

  • 它是什么? 这是一个接口,当所有模块都已加载,并且所有的 onModuleInit 钩子都已成功执行后,这个钩子才会被调用。
  • 触发时机: 全局,仅一次。它标志着整个应用已经完全准备好,即将开始接收外部请求。
  • 用途: 适合执行那些需要整个应用都处于就绪状态的全局性启动任务。比如:
    • 启动一个定时任务(Cron Job)。
    • 与消息队列建立连接。
    • 执行一个一次性的数据迁移或设置脚本。

代码示例:启动一个定时心跳日志

typescript
// app.service.ts
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';

@Injectable()
export class AppService implements OnApplicationBootstrap {
  onApplicationBootstrap() {
    console.log(
      '🚀 Application has fully bootstrapped. All modules are ready!'
    );
    console.log('Starting heartbeat log every 5 seconds...');

    // 在这里启动一个定时器是安全的,因为我们知道整个应用都已就绪
    setInterval(() => {
      console.log(
        `❤️ Heartbeat: Application is running... ${new Date().toLocaleTimeString()}`
      );
    }, 5000);
  }

  getHello(): string {
    return 'Hello World!';
  }
}

思考: 为什么不把 setInterval 放在 onModuleInit 里?因为 onModuleInit 只保证了当前模块就绪,如果你的定时任务依赖了其他模块的服务,可能会有风险。OnApplicationBootstrap 保证了万事俱备,是启动这类全局任务的最安全地点。

2. “回收序列”:应用关闭时的生命周期

一个专业的应用不仅要能平稳启动,更要能优雅地关闭 (Graceful Shutdown)。这意味着在退出前,它应该有机会完成正在处理的请求、关闭数据库连接、释放资源。

前置知识:开启“优雅关闭”模式

默认情况下,当你按下 Ctrl+C 时,NestJS 应用会立即退出。为了让关闭的生命周期事件生效,你必须在 main.ts 中明确地告诉 NestJS:“请监听关闭信号,并执行优雅关闭程序。”

src/main.ts

typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 关键!开启关闭钩子
  app.enableShutdownHooks();

  await app.listen(3000);
}
bootstrap();

现在,当你按下 Ctrl+C 时,NestJS 会捕获这个信号(SIGINT),并开始执行关闭序列。


OnModuleDestroy, OnBeforeApplicationShutdown, OnApplicationShutdown

这三个钩子控制着关闭流程:

  1. OnModuleDestroy

    • 类比: “模块资源回收”。
    • 何时触发: 在收到关闭信号后,首先被调用的清理钩子。每个实现了它的服务都会执行。
    • 用途: 清理由特定服务或模块创建的资源。最典型的例子就是关闭数据库连接。
  2. OnBeforeApplicationShutdown

    • 类比: “准备最终告别”。
    • 何时触发:OnModuleDestroy 开始执行之后,但在进程退出之前。
    • 用途: 让你有机会在应用即将关闭前执行一些最后的逻辑。比如,发送一个“服务即将下线”的通知到监控系统。
  3. OnApplicationShutdown

    • 类比: “最后的遗言”。
    • 何时触发: 最后一个被调用的钩子,在所有清理工作(包括 OnModuleDestroy)完成后。
    • 用途: 执行最终的清理或日志记录。它会接收到导致关闭的信号(如 'SIGTERM')作为参数。

代码示例:一个管理数据库连接的服务

typescript
// db-connection.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';

@Injectable()
export class DbConnectionService implements OnModuleInit, OnModuleDestroy {
  onModuleInit() {
    console.log('模拟:数据库连接已建立。');
  }

  // 当应用关闭时,这个方法会被调用
  onModuleDestroy() {
    console.log('模拟:数据库连接已优雅地关闭。');
  }
}

当你启动应用然后按 Ctrl+C 时,你会看到 "数据库连接已优雅地关闭" 的消息,证明了清理钩子被成功执行。

总结:完整的生命周期流程

这张图可以帮你理清整个流程:

启动时 (Startup)

  1. 依赖解析和模块实例化
  2. onModuleInit() 在每个模块中被调用
  3. onApplicationBootstrap() 在整个应用中被调用一次
  4. 应用开始运行

关闭时 (Shutdown)

  1. 收到关闭信号 (如 Ctrl+C)
  2. onModuleDestroy() 在每个模块中被调用
  3. onBeforeApplicationShutdown() 在整个应用中被调用一次
  4. onApplicationShutdown() 在整个应用中被调用一次
  5. NestJS 进程退出

生命周期事件是 NestJS 框架健壮性的体现。掌握它们,你就能编写出更可靠、更专业、能够从容应对启动和关闭挑战的应用程序。