【NestJS】03 全局模块与生命周期解析

1875 字
9 分钟
【NestJS】03 全局模块与生命周期解析

作为 Nest.js 开发者,你一定遇到过这些「小麻烦」:

  • 某个常用模块(比如工具类、数据库模块)被 10 个业务模块引用,每个模块都要写一遍imports: [CommonModule],烦到想复制粘贴;
  • 应用启动时需要初始化数据库连接、Redis 客户端,却不知道该把代码放在哪里;
  • 应用 shutdown 时要关闭资源连接,却找不到「正确的时机」执行清理逻辑。

别慌!Nest.js 的全局模块生命周期钩子正好解决这些问题。今天咱们就用「实际案例+代码演示」,把这两个知识点讲透~

一、全局模块:一次声明,处处可用#

先回忆下 Nest.js 的「常规操作」:如果模块 A 要使用模块 B 的 Provider,必须在模块 A 的@Moduleimports: [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. 先搞懂:生命周期的「执行顺序」#

先看启动阶段的钩子(按执行顺序):

  1. onModuleInit:模块内的Controller→Provider→Module依次执行(递归解析所有模块);
  2. onApplicationBootstrap:所有模块初始化完成后,监听端口前执行(Controller→Provider→Module)。

再看销毁阶段的钩子(按执行顺序):

  1. onModuleDestroy:模块内的Controller→Provider→Module依次执行;
  2. beforeApplicationShutdown:收到系统信号(如SIGTERM)时执行,能拿到信号类型;
  3. onApplicationShutdown:端口关闭后执行,最后清理资源。

2. 实战:用钩子打印「执行顺序日志」#

我们创建CccModuleDddModule,在 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,你会看到日志输出:

Terminal window
🎮 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 秒后会看到销毁日志:

Terminal window
🎮 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 successfully

3. 生命周期钩子的「实际用途」#

生命周期钩子的核心价值是「在特定阶段处理资源」,比如:

  • 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连接已关闭')
}
}
}

三、总结:关键知识点速记#

  1. 全局模块:用@Global()装饰器,一次声明处处可用,适用于通用基础模块;
  2. 生命周期钩子
    • 启动阶段:onModuleInit(模块内初始化)→onApplicationBootstrap(全部模块初始化);
    • 销毁阶段:onModuleDestroy(模块内清理)→beforeApplicationShutdown(收到信号)→onApplicationShutdown(最后清理);
  3. 最佳实践
    • 全局模块别滥用,业务模块尽量用普通 Import;
    • 生命周期钩子用于「资源初始化/清理」,比如数据库连接、缓存预热。

参考资料#

如果这篇文章帮你解决了「重复 Import」或「资源管理」的问题,点个赞再走~ 你的支持是我写下去的动力!🚀

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

【NestJS】03 全局模块与生命周期解析
https://blog.fridolph.top/posts/2025-09-15__nest03-module/
作者
Fridolph
发布于
2025-09-15
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录