应用生命周期
在本指南中,我们将了解 AdonisJS 如何启动你的应用,以及在应用被视为就绪之前你可以使用哪些生命周期钩子来改变应用状态。
应用的生命周期取决于其运行所处的环境。例如,为处理 HTTP 请求而启动的长生命周期进程,与短生命周期的 ace 命令,其管理方式有所不同。
那么,让我们来了解每种受支持环境的生命周期。
AdonisJS 应用如何启动
一个 AdonisJS 应用有多个入口点,每个入口点都会在特定的环境中启动应用。以下入口点文件存储在 bin 目录中。
bin/server.ts入口点启动 AdonisJS 应用来处理 HTTP 请求。当你运行node ace serve命令时,在底层我们会将此文件作为子进程运行。bin/console.ts入口点启动 AdonisJS 应用来处理 CLI 命令。该文件在底层使用 Ace。bin/test.ts入口点启动 AdonisJS 应用,使用 Japa 运行测试。
如果你打开其中任意一个文件,你会看到我们使用 Ignitor 模块来串联各项工作,然后启动应用。
Ignitor 模块封装了启动 AdonisJS 应用的逻辑。在底层,它会执行以下操作。
- 创建 Application 类的一个实例。
- 初始化/启动应用。
- 执行启动应用的主操作。例如,对于 HTTP 服务器来说,
main操作涉及启动 HTTP 服务器;而对于测试来说,main操作则涉及运行测试。
Ignitor 代码库 相对直观,因此请浏览源码以更好地理解它。
启动阶段
除 console 环境外,启动阶段在所有环境中都是相同的。在 console 环境中,由所执行的命令决定是否启动应用。
只有在应用启动之后,你才能使用容器绑定和服务。
开始阶段
开始阶段在所有环境中各不相同。此外,执行流程还会进一步划分为以下子阶段:
-
pre-start(启动前)阶段指的是在启动应用之前执行的操作。 -
post-start(启动后)阶段指的是在启动应用之后执行的操作。对于 HTTP 服务器而言,这些操作会在 HTTP 服务器准备接受新连接之后执行。
在 web 环境中
在 web 环境中,会创建一个长生命周期的 HTTP 连接来监听传入的请求,应用会保持 ready 状态,直到服务器崩溃或进程收到关闭信号。
在 test 环境中
在 test 环境中,会执行 pre-start 和 post-start 操作。之后,我们会导入测试文件并执行测试。
在 console 环境中
在 console 环境中,由所执行的命令决定是否启动应用。
命令可以通过启用 options.startApp 标志来启动应用。其结果就是,pre-start 和 post-start 操作会在命令的 run 方法之前运行。
import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
static options = {
startApp: true
}
async run() {
console.log(this.app.isReady) // true
}
}
终止阶段
应用的终止在短生命周期与长生命周期进程之间差异很大。
短生命周期的命令或测试进程会在主操作结束后开始终止。
长生命周期的 HTTP 服务器进程会等待 SIGTERM 等退出信号来开始终止过程。
响应进程信号
在所有环境中,当应用收到 SIGTERM 信号时,我们都会开始一个优雅关闭过程。如果你使用 pm2 启动了应用,优雅关闭会在收到 SIGINT 事件后发生。
在 web 环境中
在 web 环境中,应用会一直运行,直到底层的 HTTP 服务器因错误而崩溃。在这种情况下,我们会开始终止应用。
在 test 环境中
优雅终止会在所有测试执行完后开始。
在 console 环境中
在 console 环境中,应用的终止取决于所执行的命令。
应用会在命令执行完毕后立即终止,除非启用了 options.staysAlive 标志;在这种情况下,命令应当显式地终止应用。
import { BaseCommand } from '@adonisjs/core/ace'
export default class GreetCommand extends BaseCommand {
static options = {
startApp: true,
staysAlive: true,
}
async run() {
await runSomeProcess()
// 终止进程
await this.terminate()
}
}
生命周期钩子
生命周期钩子允许你挂载到应用的引导过程中,并在应用经历不同状态时执行操作。
你可以使用服务提供者类注册钩子,或者在应用类上内联定义它们。
内联回调
你应该在应用实例创建后立即注册生命周期钩子。
入口点文件 bin/server.ts、bin/console.ts 和 bin/test.ts 会为不同的环境创建一个全新的应用实例,你可以在这些文件中注册内联回调。
const app = new Application(new URL('../', import.meta.url))
new Ignitor(APP_ROOT, { importer: IMPORTER })
.tap((app) => {
app.booted(() => {
console.log('invoked after the app is booted')
})
app.ready(() => {
console.log('invoked after the app is ready')
})
app.terminating(() => {
console.log('invoked before the termination starts')
})
})
-
initiating:在应用进入 initiated 状态之前调用该钩子操作。adonisrc.ts文件会在执行完initiating钩子之后被解析。 -
booting:在启动应用之前调用该钩子操作。booting钩子执行完之后才会导入 config 文件。 -
booted:在所有服务提供者都已被注册并启动之后调用该钩子操作。 -
starting:在导入预加载文件之前调用该钩子操作。 -
ready:在应用就绪之后调用该钩子操作。 -
terminating:一旦优雅退出过程开始就调用该钩子操作。例如,此钩子可以关闭数据库连接或结束打开的流。
使用服务提供者
服务提供者将生命周期钩子定义为提供者类中的方法。我们建议使用服务提供者而非内联回调,因为它们能让一切保持井然有序。
以下是可用生命周期方法的列表。
import { ApplicationService } from '@adonisjs/core/types'
export default class AppProvider {
constructor(protected app: ApplicationService) {}
register() {
}
async boot() {
}
async start() {
}
async ready() {
}
async shutdown() {
}
}
-
register:register 方法在容器内注册绑定。该方法的本质设计为同步执行。 -
boot:boot 方法用于启动或初始化你在容器内注册的绑定。 -
start:start 方法在ready方法之前运行。它让你能够执行那些ready钩子操作可能需要用到的动作。 -
ready:ready 方法在应用被视为就绪之后运行。 -
shutdown:当应用开始优雅关闭时调用 shutdown 方法。你可以使用此方法关闭数据库连接,或结束已打开的流。