生命周期事件
好的,我们来探索 NestJS 世界中一个非常重要的机制,它能让你精确地控制应用在启动和关闭过程中的行为。这就是生命周期事件 (Lifecycle Events)。
掌控应用的“脉搏”:NestJS 生命周期事件全解析
想象一下发射一枚精密的火箭。
整个过程必须遵循一个严格的、预先设定的顺序:
- 发射前自检:每个独立的部件(燃料泵、导航系统)都要先完成内部的初始化和检查。
- 主引擎点火:在所有部件都报告“正常”后,总控中心下达指令,点燃主引擎,准备升空。
- 任务执行:火箭在太空中平稳运行。
- 任务结束 & 回收:在任务完成后,需要执行一系列关闭程序,比如分离推进器、关闭引擎、释放降落伞等,以确保安全回收。
一个健壮的 NestJS 应用,其生命周期也像这次火箭发射一样,拥有一套清晰、可预测的事件序列。生命周期事件就是 NestJS 暴露给你的“钩子”(Hooks),让你可以在这些关键的“时间点”上,植入你自己的代码,执行特定的任务。
1. “发射序列”:应用启动时的生命周期
当你的 NestJS 应用启动时,主要会经历两个重要的生命周期事件。
OnModuleInit
- “各模块自检”
- 它是什么? 这是一个接口,当一个服务所在的模块(Host Module)完成了所有依赖的解析和注入后,这个钩子就会被调用。
- 触发时机: 在模块内部。每个模块都有自己的
OnModuleInit
阶段。 - 用途: 非常适合执行那些依赖于本模块其他服务的初始化逻辑。比如,一个
CacheService
需要在启动后,立即从DatabaseService
获取一些初始数据填充到缓存中。
代码示例:CacheService
初始化
假设我们有一个 UsersModule
,里面有 DbService
和 UsersService
。我们希望 UsersService
在一启动时就从数据库加载管理员列表。
src/users/db.service.ts
@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
// 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)。
- 与消息队列建立连接。
- 执行一个一次性的数据迁移或设置脚本。
代码示例:启动一个定时心跳日志
// 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
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
这三个钩子控制着关闭流程:
OnModuleDestroy
- 类比: “模块资源回收”。
- 何时触发: 在收到关闭信号后,首先被调用的清理钩子。每个实现了它的服务都会执行。
- 用途: 清理由特定服务或模块创建的资源。最典型的例子就是关闭数据库连接。
OnBeforeApplicationShutdown
- 类比: “准备最终告别”。
- 何时触发: 在
OnModuleDestroy
开始执行之后,但在进程退出之前。 - 用途: 让你有机会在应用即将关闭前执行一些最后的逻辑。比如,发送一个“服务即将下线”的通知到监控系统。
OnApplicationShutdown
- 类比: “最后的遗言”。
- 何时触发: 最后一个被调用的钩子,在所有清理工作(包括
OnModuleDestroy
)完成后。 - 用途: 执行最终的清理或日志记录。它会接收到导致关闭的信号(如
'SIGTERM'
)作为参数。
代码示例:一个管理数据库连接的服务
// 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)
- 依赖解析和模块实例化
onModuleInit()
在每个模块中被调用onApplicationBootstrap()
在整个应用中被调用一次- 应用开始运行
关闭时 (Shutdown)
- 收到关闭信号 (如
Ctrl+C
) onModuleDestroy()
在每个模块中被调用onBeforeApplicationShutdown()
在整个应用中被调用一次onApplicationShutdown()
在整个应用中被调用一次- NestJS 进程退出
生命周期事件是 NestJS 框架健壮性的体现。掌握它们,你就能编写出更可靠、更专业、能够从容应对启动和关闭挑战的应用程序。