本系列文已出版成書「NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式」,感謝 iT 邦幫忙與博碩文化的協助。如果對 NestJS 有興趣、覺得這個系列文對你有幫助的話,歡迎前往購書,你的支持是我最大的寫作動力!
在開始介紹之前,先來了解一下何謂 生命週期 (Lifecycle),最簡單的舉例是人,人從出生到死亡就是一個完整的生命週期。那什麼是 生命週期鉤子 (Lifecycle hook) ?即在生命週期中某個時間點會觸發的事件,比如:小明在出生之後被賦予了國民身份、 5 歲的時候會上幼稚園等。在程式設計領域中,也有所謂的生命週期,最簡單的分法為:開始到結束,有些框架甚至會設計 Lifecycle Hook 來針對不同時間點觸發不同的事件,比如說:啟動時先呼叫 API、關閉時留下 Log 資訊等。
Nest 也有設計 Lifecycle Hook,按照順序排列共分成下方五個時間點:
這裡可以看出 Lifecycle Hooks 發生在「啟動」與「關閉」這兩個時間點,而它們可以在 modules
、controllers
、injectables
被觸發。需特別注意的是,在關閉時間點的 Hook 必須要在 bootstrap
執行時調用 app.enableShutdownHooks()
來開啟此功能,會在執行 app.close()
或收到系統關閉訊號時 (Ctrl + C
) 被觸發。
注意:由於關閉時執行的 Hook 會消耗較多的效能在監聽事件上,故預設是不啟用的。
onModuleInit
會在該模組的依賴項目處理完畢時被調用。假設 Nest App 有 AppModule
與 TodoModule
,並在 AppModule
引入了 TodoModule
:
AppModule
會先被載入,載入的時候會去讀取它的依賴項目:TodoModule
、AppController
、AppService
,當這些依賴項目處理完畢後,也就是依賴項目會先呼叫 onModuleInit
,AppModule
才會調用其 onModuleInit
,順序如下圖所示:
使用方式很簡單,假如我們要在 AppModule
使用 onModuleInit
,就讓它實作 OnModuleInit
介面,並添加 onModuleInit
方法:
import { Module, OnModuleInit } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
],
controllers: [
AppController
],
providers: [
AppService
]
})
export class AppModule implements OnModuleInit {
onModuleInit(): void {
console.log('[AppModule]: initial event!');
}
}
如此一來,便會在初始化階段於終端機顯示下方字串:
[AppModule]: initial event!
onApplicationBootstrap
在 Nest App 初始化所有模組後進行調用,會發生在連線建立前。與 onModuleInit
的執行順序相同,會先執行依賴項目的 onApplicationBootstrap
。
假如我們要在 AppModule
使用 onApplicationBootstrap
,就讓它實作 OnApplicationBootstrap
介面,並添加 onApplicationBootstrap
方法:
import { Module, OnApplicationBootstrap } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
],
controllers: [
AppController
],
providers: [
AppService
]
})
export class AppModule implements OnApplicationBootstrap {
onApplicationBootstrap() {
console.log('[AppModule]: bootstrap event!');
}
}
如此一來,便會在啟動後之階段於終端機顯示下方字串:
[AppModule]: bootstrap event!
onModuleDestroy
在接收到系統關閉訊號或 app.close()
時調用。與 onModuleInit
的執行順序 不同,會從 AppModule
的 Controller 與 Provider 開始執行 onModuleDestroy
,執行完之後 AppModule
就會觸發 onModuleDestroy
,接著其依賴項目才會依序執行該 Hook,順序如下圖所示:
注意:在 Nest 第 8 版中,
onModuleDestroy
的執行順序與onModuleInit
相同,感謝熱心的 mihuartuanr 提醒。
假如我們要在 AppModule
使用 onModuleDestroy
,就讓它實作 OnModuleDestroy
介面,並添加 onModuleDestroy
方法:
import { Module, OnModuleDestroy } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
TodoModule,
UserModule
],
controllers: [
AppController
],
providers: [
AppService
]
})
export class AppModule implements OnModuleDestroy {
onModuleDestroy(): void {
console.log('[AppModule]: destroy event!');
}
}
如此一來,便會在關閉時於終端機顯示下方字串:
[AppModule]: destroy event!
beforeApplicationShutdown
在 Nest App 關閉所有連線之前調用,並會觸發 app.close()
。與 onModuleInit
的執行順序相同,會先執行依賴項目的 beforeApplicationShutdown
。
假如我們要在 AppModule
使用 beforeApplicationShutdown
,就讓它實作 BeforeApplicationShutdown
介面,並添加 beforeApplicationShutdown
方法:
import { BeforeApplicationShutdown, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
],
controllers: [
AppController
],
providers: [
AppService
]
})
export class AppModule implements BeforeApplicationShutdown {
beforeApplicationShutdown(): void {
console.log('[AppModule]: before shutdown event!');
}
}
如此一來,便會在關閉連線前之階段於終端機顯示下方字串:
[AppModule]: before shutdown event!
onApplicationShutdown
在 Nest App 關閉所有連接時進行調用。與 onModuleInit
的執行順序相同,會先執行依賴項目的 onApplicationShutdown
。
假如我們要在 AppModule
使用 onApplicationShutdown
,就讓它實作 OnApplicationShutdown
介面,並添加 onApplicationShutdown
方法:
import { Module, OnApplicationShutdown } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
],
controllers: [
AppController
],
providers: [
AppService
]
})
export class AppModule implements OnApplicationShutdown {
onApplicationShutdown(): void {
console.log('[AppModule]: shutdown event!');
}
}
如此一來,便會在關閉時於終端機顯示下方字串:
[AppModule]: shutdown event!
運用 Lifecycle Hooks 可以有效地在適當時機點做適當的動作,以關閉時調用的 Hook 來說,通常會用在 Kubernetes 等服務上。這裡附上今天的懶人包:
onModuleInit
、onApplicationBootstrap
、onModuleDestroy
、beforeApplicationShutdown
與 onApplicationShutdown
。app.enableShutdownHooks()
來啟用此功能。您好:
onModuleDestroy 在接收到系統關閉訊號或 app.close() 時調用。與 onModuleInit 的執行順序 不同,會從 AppModule 的 Controller 與 Provider 開始執行 onModuleDestroy,執行完之後 AppModule 就會觸發 onModuleDestroy,接著其依賴項目才會依序執行該 Hook
这里我做了如下试验:
// app.module.ts
@Module({
imports: [
TodoModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule
implements
OnModuleInit,
OnModuleDestroy,
OnApplicationBootstrap,
BeforeApplicationShutdown,
OnApplicationShutdown
{
onModuleInit(): void {
console.log('[AppModule]: init');
}
onModuleDestroy(): void {
console.log('[AppModule]: destroy');
}
onApplicationBootstrap(): void {
console.log('[AppModule]: bootstrap');
}
beforeApplicationShutdown(): void {
console.log('[AppModule]: before shutdown event!');
}
onApplicationShutdown(): void {
console.log('[AppModule]: shutdown event!');
}
}
// todo.module.ts
@Module({
controllers: [TodoController],
providers: [HANDSOME_HAO],
})
export class TodoModule
implements
NestModule,
OnModuleInit,
OnModuleDestroy,
OnApplicationBootstrap,
BeforeApplicationShutdown,
OnApplicationShutdown
{
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware, TestMiddleware, AddUserMiddleware)
.forRoutes('/todo');
}
onModuleInit(): void {
console.log('[TodoModule]: init');
}
onModuleDestroy(): void {
console.log('[TodoModule]: destroy');
}
onApplicationBootstrap(): void {
console.log('[TodoModule]: bootstrap');
}
beforeApplicationShutdown(): void {
console.log('[TodoModule]: before shutdown event!');
}
onApplicationShutdown(): void {
console.log('[TodoModule]: shutdown event!');
}
}
在通过Ctrl + C
终止程序时,顺序如下:
[TodoController]: init
[TodoModule]: init
[AppController]: init
[AppService]: init
[AppModule]: init
[TodoController]: bootstrap
[TodoModule]: bootstrap
[AppController]: bootstrap
[AppService]: bootstrap
[AppModule]: bootstrap
[Nest] 14140 - 2021-11-12 17:21:22 LOG [NestApplication] Nest application successfully started +15ms
[TodoController]: destroy
[TodoModule]: destroy
[AppController]: destroy
[AppService]: destroy
[AppModule]: destroy
[TodoController]: before shutdown event!
[TodoModule]: before shutdown event!
[AppController]: before shutdown event!
[AppService]: before shutdown event!
[AppModule]: before shutdown event!
[TodoController]: shutdown event!
[TodoModule]: shutdown event!
[AppController]: shutdown event!
[AppService]: shutdown event!
[AppModule]: shutdown event!
看执行输出,onModuleDestroy與 onModuleInit 的執行順序相同,是不是我对这块的理解有问题呢?
你好,經查證發現這是 Nest 版本差異所致,由於我在 trace source code 以及親自實驗時是用 v7,後來升上 v8 沒有注意到執行順序改變了,我會在文章註明一下,謝謝提醒!
另外,我有去 trace 什麼時候調整的,可以參考這個 commit 與 PR,有問題歡迎互相交流!