作为 Nest.js 开发者,你一定遇到过这些「小麻烦」:
- 某个常用模块(比如工具类、数据库模块)被 10 个业务模块引用,每个模块都要写一遍
imports: [CommonModule],烦到想复制粘贴; - 应用启动时需要初始化数据库连接、Redis 客户端,却不知道该把代码放在哪里;
- 应用 shutdown 时要关闭资源连接,却找不到「正确的时机」执行清理逻辑。
别慌!Nest.js 的全局模块和生命周期钩子正好解决这些问题。今天咱们就用「实际案例+代码演示」,把这两个知识点讲透~
一、全局模块:一次声明,处处可用
先回忆下 Nest.js 的「常规操作」:如果模块 A 要使用模块 B 的 Provider,必须在模块 A 的@Module里imports: [BModule]。比如下面这个例子:
1. 常规模块引入:重复 Import 的痛点
假设我们有AaaModule(提供AaaService)和BbbModule(需要用AaaService):
// src/aaa/aaa.module.ts
import { Module } from '@nestjs/common'
import { AaaService } from './aaa.service'
@Module({
providers: [AaaService],
exports: [AaaService], // 导出AaaService,供其他模块使用
})
export class AaaModule {}// src/bbb/bbb.module.ts
import { Module } from '@nestjs/common'
import { BbbService } from './bbb.service'
import { AaaModule } from '../aaa/aaa.module' // 必须Import
@Module({
imports: [AaaModule], // 引入AaaModule才能用AaaService
providers: [BbbService],
})
export class BbbModule {}然后在BbbService里注入AaaService:
// src/bbb/bbb.service.ts
import { Injectable } from '@nestjs/common'
import { AaaService } from '../aaa/aaa.service'
@Injectable()
export class BbbService {
constructor(private readonly aaaService: AaaService) {} // 注入成功
doSomething() {
return this.aaaService.getInfo() // 使用AaaService的方法
}
}问题来了:如果AaaModule要被 10 个模块引用,每个模块都得写一遍imports: [AaaModule]——重复劳动不说,还容易漏写!
2. 全局模块改造:@Global 装饰器拯救你
Nest.js 提供了@Global()装饰器,给模块加上这个装饰器,它的导出 Provider 会变成「全局可用」,不需要再在其他模块里import。
改造AaaModule:
// src/aaa/aaa.module.ts
import { Module, Global } from '@nestjs/common' // 引入Global装饰器
import { AaaService } from './aaa.service'
@Global() // 声明为全局模块
@Module({
providers: [AaaService],
exports: [AaaService], // 依然需要导出,否则全局也拿不到
})
export class AaaModule {}然后修改BbbModule:删掉 AaaModule 的 Import
// src/bbb/bbb.module.ts
import { Module } from '@nestjs/common'
import { BbbService } from './bbb.service'
@Module({
providers: [BbbService], // 不用Import AaaModule!
})
export class BbbModule {}再跑服务验证:npm run start:dev,你会发现BbbService依然能正常注入AaaService——全局模块生效了!
3. 全局模块的「使用准则」:别滥用!
全局模块虽然方便,但一定要克制:
- 全局模块的 Provider「来源不明确」,新同事接手代码时,可能不知道某个 Service 是从哪个模块来的;
- 全局模块会增加依赖耦合,比如修改 AaaModule 的导出,可能影响所有用到它的模块。
建议:只给「通用基础模块」用全局,比如工具类模块、数据库连接模块(如 TypeORM/Mongoose 模块),业务模块尽量不用。
二、生命周期钩子:掌控应用的「启停逻辑」
Nest.js 的应用启动和销毁过程,就像一场「有序的舞台剧」:
- 启动时:先解析模块依赖 → 初始化 Provider 和 Controller→ 监听端口;
- 销毁时:先清理资源 → 关闭端口 → 退出进程。
Nest.js 暴露了7 个生命周期钩子,让你能在「特定阶段」执行自定义逻辑(比如初始化数据库、关闭连接)。
1. 先搞懂:生命周期的「执行顺序」
先看启动阶段的钩子(按执行顺序):
onModuleInit:模块内的Controller→Provider→Module依次执行(递归解析所有模块);onApplicationBootstrap:所有模块初始化完成后,监听端口前执行(Controller→Provider→Module)。
再看销毁阶段的钩子(按执行顺序):
onModuleDestroy:模块内的Controller→Provider→Module依次执行;beforeApplicationShutdown:收到系统信号(如SIGTERM)时执行,能拿到信号类型;onApplicationShutdown:端口关闭后执行,最后清理资源。
2. 实战:用钩子打印「执行顺序日志」
我们创建CccModule和DddModule,在 Controller、Service、Module 里分别实现生命周期钩子,看日志输出:
步骤 1:创建模块并实现钩子
以CccModule为例(DddModule同理):
// src/ccc/ccc.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
@Injectable()
export class CccService implements OnModuleInit, OnModuleDestroy {
onModuleInit() {
console.log('🔧 CccService → onModuleInit')
}
onModuleDestroy() {
console.log('🔧 CccService → onModuleDestroy')
}
findAll() {
return ['ccc-data']
}
}// src/ccc/ccc.controller.ts
import { Controller, Get, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { CccService } from './ccc.service'
@Controller('ccc')
export class CccController implements OnModuleInit, OnModuleDestroy {
constructor(private readonly cccService: CccService) {}
onModuleInit() {
console.log('🎮 CccController → onModuleInit')
}
onModuleDestroy() {
console.log('🎮 CccController → onModuleDestroy')
}
@Get()
findAll() {
return this.cccService.findAll()
}
}// src/ccc/ccc.module.ts
import {
Module,
OnModuleInit,
OnModuleDestroy,
OnApplicationBootstrap,
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,
OnModuleDestroy,
OnApplicationBootstrap,
BeforeApplicationShutdown,
OnApplicationShutdown
{
constructor(private readonly moduleRef: ModuleRef) {} // 注入ModuleRef,用于获取Provider
// 启动阶段钩子
onModuleInit() {
console.log('📦 CccModule → onModuleInit')
}
onApplicationBootstrap() {
console.log('📦 CccModule → onApplicationBootstrap')
}
// 销毁阶段钩子
onModuleDestroy() {
console.log('📦 CccModule → onModuleDestroy')
}
beforeApplicationShutdown(signal: string) {
console.log(`📦 CccModule → beforeApplicationShutdown(信号:${signal})`)
}
onApplicationShutdown() {
const cccService = this.moduleRef.get<CccService>(CccService) // 从模块中获取Service
console.log(
`📦 CccModule → onApplicationShutdown(Service数据:${cccService.findAll()})`
)
}
}步骤 2:验证启动阶段的钩子顺序
跑服务npm run start:dev,你会看到日志输出:
🎮 CccController → onModuleInit
🔧 CccService → onModuleInit
📦 CccModule → onModuleInit
🎮 DddController → onModuleInit
🔧 DddService → onModuleInit
📦 DddModule → onModuleInit
🎮 CccController → onApplicationBootstrap
🔧 CccService → onApplicationBootstrap
📦 CccModule → onApplicationBootstrap
🎮 DddController → onApplicationBootstrap
🔧 DddService → onApplicationBootstrap
📦 DddModule → onApplicationBootstrap
[Nest] 12345 - 09/20/2024, 10:00:00 AM LOG [NestFactory] Starting Nest application...
[Nest] 12345 - 09/20/2024, 10:00:01 AM LOG [NestApplication] Nest application successfully started结论:启动阶段是「Controller→Service→Module」的顺序,递归执行所有模块。
步骤 3:验证销毁阶段的钩子顺序
我们在main.ts里加一段代码,3 秒后触发销毁:
// src/main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
await app.listen(3000)
// 3秒后触发销毁逻辑
setTimeout(async () => {
await app.close() // 触发销毁钩子,但不会退出进程
}, 3000)
}
bootstrap()重新跑服务,3 秒后会看到销毁日志:
🎮 CccController → onModuleDestroy
🔧 CccService → onModuleDestroy
📦 CccModule → onModuleDestroy
🎮 DddController → onModuleDestroy
🔧 DddService → onModuleDestroy
📦 DddModule → onModuleDestroy
📦 CccModule → beforeApplicationShutdown(信号:SIGTERM)
📦 DddModule → beforeApplicationShutdown(信号:SIGTERM)
[Nest] 12345 - 09/20/2024, 10:00:03 AM LOG [NestApplication] Nest application is closing...
📦 CccModule → onApplicationShutdown(Service数据:ccc-data)
📦 DddModule → onApplicationShutdown(Service数据:ddd-data)
[Nest] 12345 - 09/20/2024, 10:00:03 AM LOG [NestApplication] Nest application closed successfully3. 生命周期钩子的「实际用途」
生命周期钩子的核心价值是「在特定阶段处理资源」,比如:
onModuleInit:初始化数据库连接、Redis 客户端;onApplicationBootstrap:预热缓存、加载配置文件;onApplicationShutdown:关闭数据库连接、清理临时文件。
举个实际例子:用onApplicationShutdown关闭 TypeORM 连接:
// src/database/database.module.ts
import { Module, OnApplicationShutdown } from '@nestjs/common'
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'
import { ModuleRef } from '@nestjs/core'
import { Connection } from 'typeorm'
@Module({
imports: [
TypeOrmModule.forRoot({
/* TypeORM配置 */
} as TypeOrmModuleOptions),
],
})
export class DatabaseModule implements OnApplicationShutdown {
constructor(private moduleRef: ModuleRef) {}
onApplicationShutdown() {
const connection = this.moduleRef.get<Connection>(Connection) // 获取TypeORM连接
if (connection.isConnected) {
await connection.close() // 关闭连接
console.log('💾 TypeORM连接已关闭')
}
}
}三、总结:关键知识点速记
- 全局模块:用
@Global()装饰器,一次声明处处可用,适用于通用基础模块; - 生命周期钩子:
- 启动阶段:
onModuleInit(模块内初始化)→onApplicationBootstrap(全部模块初始化); - 销毁阶段:
onModuleDestroy(模块内清理)→beforeApplicationShutdown(收到信号)→onApplicationShutdown(最后清理);
- 启动阶段:
- 最佳实践:
- 全局模块别滥用,业务模块尽量用普通 Import;
- 生命周期钩子用于「资源初始化/清理」,比如数据库连接、缓存预热。
参考资料
如果这篇文章帮你解决了「重复 Import」或「资源管理」的问题,点个赞再走~ 你的支持是我写下去的动力!🚀
- 本文链接:https://fridolph.top/posts/2025-09-15__nest03-module
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 许可协议。