- 后端
- 微服务
- 云原生
【免费下载链接】midway
🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈
在 Node.js 服务端应用中,数据库连接、配置预生成、资源清理这类操作理应发生在应用启动与停止的特定阶段,而不是在请求响应中重复处理。Midway 为此内置了一套完整的生命周期机制:你只需在src/configuration.ts中实现ILifeCycle接口,框架就会在启动、就绪、服务启动、关闭等关键节点自动调用对应方法。读完本文,你将掌握 Midway 项目生命周期与对象生命周期的全部钩子函数、其底层执行顺序与超时机制,并能在真实项目中完成连接建立、对象扩充、健康检查与优雅关闭的落地。
项目生命周期概览
Midway 为开发人员提供了四个项目级生命周期函数,分别对应应用运行的不同阶段:
onConfigLoad:配置文件加载阶段,可以在这里修改、补充配置;onReady:依赖注入容器准备完毕,可以在这个阶段做大部分初始化事情;onServerReady:服务启动完成,此时可以拿到 server 与端口信息;onStop:应用即将关闭,在这里清理资源;onHealthCheck(可选补充):在健康检查被调用时执行,用于上报组件/依赖的健康状态。
这些钩子通过项目根目录下的src/configuration.ts文件实现ILifeCycle接口即可被框架在启动时自动加载。接口的完整定义如下(与仓库源码 interface.ts 中ILifeCycle的定义保持一致):
interface ILifeCycle { /** * 在应用配置加载后执行 */ onConfigLoad?(container: IMidwayContainer, app: IMidwayApplication): Promise<void>; /** * 在依赖注入容器 ready 的时候执行 */ onReady(container: IMidwayContainer, app: IMidwayApplication): Promise<void>; /** * 在应用服务启动后执行 */ onServerReady?(container: IMidwayContainer, app: IMidwayApplication): Promise<void>; /** * 在应用停止的时候执行 */ onStop?(container: IMidwayContainer, app: IMidwayApplication): Promise<void>; /** * 在健康检查时执行 */ onHealthCheck?(container: IMidwayContainer): Promise<HealthResult>; }从源码实现看,每个钩子函数实际上还会接收到第三个参数options: LifeCycleInvokeOptions,其中包含timeout(当前生命周期超时配置)与abortController(中断信号),这在后文"超时机制"一节会详细展开。
底层执行顺序
生命周期方法并非并行执行,而是由框架的MidwayLifeCycleService按严格顺序调度。在 lifeCycleService.ts 中可以清晰看到完整执行链:
- 扫描所有
@Configuration()修饰的类,创建实例并收集为生命周期实例列表; - 并行绑定四类对象生命周期钩子(
onBeforeObjectCreated/onObjectCreated/onObjectInit/onBeforeObjectDestroy); - 依次执行所有配置类的
onConfigLoad(),返回的数据通过configService.addObject()合并进配置; - 依次执行
onReady(); - 调用
frameworkService.runFramework()启动框架(此时监听端口、启动 server); - 依次执行
onServerReady()。
而在应用停止时(stop 方法),会先将生命周期实例列表倒序后依次执行onStop(),再停止框架。也就是说,onStop的执行顺序与启动阶段相反,后初始化的组件会先被清理,从而保证资源释放的对称性。
onConfigLoad:启动时修改配置
onConfigLoad一般用于修改项目的配置文件,比如根据运行时环境预生成部分配置、注入密钥等。它的返回值会被自动合并进全局配置:
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer } from '@midwayjs/core'; @Configuration() export class MainConfiguration implements ILifeCycle { async onConfigLoad(): Promise<void> { // 直接返回数据,会自动合并到配置中 return { test: 1 } } }此时,在其他类中通过@Config拿到的配置就包含了这段返回的数据,例如@Config('test')即可获取到1。这种"异步初始化配置"的更多玩法,可参考 异步初始化配置 章节。
从源码角度印证:MidwayLifeCycleService在调用onConfigLoad时设置了resultHandler,将每个配置类返回的非空数据通过configService.addObject(configData)合并到配置服务中(见 lifeCycleService.ts)。因此返回值的结构会作为配置对象的一部分直接暴露给@Config注入。
onReady:容器就绪后的主要初始化入口
onReady是大部分场景下都会使用到的生命周期。
:::info 注意,这里的 ready 指的是依赖注入容器 ready,并不是应用 ready,所以你可以对应用做任意扩展,比如添加中间件、连接数据库等。 :::
典型场景:在初始化时提前连接数据库。由于配置类本身也是由容器管理的对象,因此可以在其中通过@Inject装饰器注入数据库连接工具类(该实例包含connect和close两个函数):
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer } from '@midwayjs/core'; @Configuration() export class MainConfiguration implements ILifeCycle { @Inject() db: any; async onReady(container: IMidwayContainer): Promise<void> { // 建立数据库连接 await this.db.connect(); } async onStop(): Promise<void> { // 关闭数据库连接 await this.db.close(); } }这样,我们就能在应用启动时建立数据库连接,而不是在请求响应时再去创建;同时,在应用停止时也能优雅地关闭数据库连接,避免连接泄漏。
通过 registerObject 扩充注入对象
除了消费容器中已有的对象,onReady阶段还可以对默认注入的对象做扩充:将三方包、外部实例注册进容器,之后在任何类中都可以直接注入使用。
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer } from '@midwayjs/core'; import * as sequelize from 'sequelize'; @Configuration() export class MainConfiguration implements ILifeCycle { async onReady(container: IMidwayContainer): Promise<void> { // 三方包对象 container.registerObject('sequelize', sequelize); } }在其他的类中可以直接注入使用:
export class IndexHandler { @Inject() sequelize; async handler() { console.log(this.sequelize); } }这里container.registerObject注册的对象与@Provide()创建的对象一样,都遵循容器的依赖注入规则,任何通过@Inject声明同名标识符的类都能拿到该实例。
onServerReady:获取 server 与服务端口
当需要获取框架的服务对象、端口等信息时,就需要用到onServerReady。因为此时框架已完成启动(frameworkService.runFramework()已执行完毕),server 实例已经可用。
以@midwayjs/koa为例,在启动时获取它的 Server:
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer } from '@midwayjs/core'; import * as koa from '@midwayjs/koa'; @Configuration({ imports: [koa] }) export class MainConfiguration implements ILifeCycle { async onServerReady(container: IMidwayContainer): Promise<void> { // 获取到 koa 中暴露的 Framework const framework = await container.getAsync(koa.Framework); const server = framework.getServer(); // ... } }这里的关键在于container.getAsync(koa.Framework):@midwayjs/koa包导出的Framework类已被注册进依赖注入容器,异步获取其单例后即可通过framework.getServer()拿到底层 HTTP Server,进而读取监听端口、注册服务端事件或做端口相关操作。仓库测试夹具 base-app-object-lifecycle/src/configuration.ts 也演示了在onServerReady中通过@App()拿到应用实例并设置属性的用法。
onStop:优雅清理资源
应用即将关闭时,onStop会被执行,适合清理资源,比如关闭数据库连接、断开消息队列、释放定时器等。
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer } from '@midwayjs/core'; import * as koa from '@midwayjs/koa'; @Configuration({ imports: [koa] }) export class MainConfiguration implements ILifeCycle { @Inject() db: any; async onReady(container: IMidwayContainer): Promise<void> { // 建立数据库连接 await this.db.connect(); } async onStop(): Promise<void> { // 关闭数据库连接 await this.db.close(); } }onHealthCheck:健康检查上报
当内置的健康检查服务调用状态获取 API 时,所有组件的onHealthCheck方法都会被自动执行。每个组件可以在该方法中探测自己依赖的状态(数据库、缓存、外部服务等),并返回统一的HealthResult结构。
下面模拟了一个 db 健康检查的方法:
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer, HealthResult } from '@midwayjs/core'; @Configuration({ namespace: 'db' }) export class MainConfiguration implements ILifeCycle { @Inject() db: any; async onReady(container: IMidwayContainer): Promise<void> { await this.db.connect(); } async onHealthCheck(): Promise<HealthResult> { try { const result = await this.db.isConnect(); if (result) { return { status: true, }; } else { return { status: false, reason: 'db is disconnect', }; } } catch (err) { return { status: false, reason: err.message, }; } } }上述onHealthCheck中,调用了一个isConnect的状态检查,根据结果返回了固定的HealthResult类型格式。
HealthResult 与聚合结果
HealthResult的定义位于 interface.ts:必须包含status: boolean,可选包含reason?: string(失败原因)。所有组件的检查结果会由MidwayHealthService.getStatus()聚合成HealthResults:
status:整体状态,只要存在任一失败的组件即为false;namespace:第一个失败组件的命名空间;reason:第一个失败原因;results:每个组件的独立检查结果数组。
从 healthService.ts 的实现可以看到两个强约束:若某个onHealthCheck的返回值不是包含status字段的对象,该组件会被记为status: false并提示"configuration.onHealthCheck return value must be object and contain status field";若方法抛错或超时,则捕获异常并将reason记为错误信息。这些行为在仓库测试 feature.test.ts 中均有对应的断言验证。
健康检查的注意事项
注意,外部调用onHealthCheck可能会非常频繁,请尽可能保持检查逻辑的可靠性和效率,确保不会对检查依赖有较大的压力。同时请自行处理检查超时后资源释放的逻辑,避免资源频繁请求却未返回结果,导致内存泄露的风险。例如在探测远端服务时,应给探测请求自身设置合理的超时时间,并在 catch 中兜底返回status: false。
全局对象生命周期
所谓对象生命周期,指的是每个对象在依赖注入容器中创建、销毁的事件。通过这些生命周期,我们可以在对象创建后、销毁时做一些统一操作,例如给所有业务对象附加通用属性、统一替换对象、执行通用的初始化或清理逻辑。
export interface IObjectLifeCycle { onBeforeObjectCreated(/**...**/); onObjectCreated(/**...**/); onObjectInit(/**...**/); onBeforeObjectDestroy(/**...**/); }ILifeCycle定义中已经包含了这些阶段(ILifeCycle extends Partial<IObjectLifeCycle>),所以直接在配置类中实现对应方法即可。
:::caution 注意,对象生命周期 API 会影响整个依赖注入容器以及业务的使用,请谨慎操作。 :::
完整接口见 interface.ts,包含onBeforeBind、onBeforeObjectCreated、onObjectCreated、onObjectInit、onBeforeObjectDestroy五个阶段。需要强调的是,这些钩子对容器中每一个对象生效,属于全局拦截,因此使用时要特别小心,避免影响框架内部对象或产生意外的副作用。
onBeforeObjectCreated:实例创建前
在业务对象实例创建前执行。注意:框架内部的某些对象由于已经初始化,无法被拦截。
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer, ObjectBeforeCreatedOptions } from '@midwayjs/core'; @Configuration() export class MainConfiguration implements ILifeCycle { async onBeforeObjectCreated(Clzz: new (...args), options: ObjectBeforeCreatedOptions): Promise<void> { // ... } }这里入参有两个参数:
Clzz当前待创建对象的原型类options一些参数
参数如下:
| 属性 | 类型 | 描述 |
|---|---|---|
| options.context | IMidwayContainer | 依赖注入容器本身 |
| options.definition | IObjectDefinition | 对象定义 |
| options.constructorArgs | any[] | 构造器入参 |
onObjectCreated:实例创建后,可替换对象
在对象实例创建后执行,这个阶段可以替换创建的对象,也可以给对象附加属性。
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer, ObjectCreatedOptions } from '@midwayjs/core'; @Configuration() export class MainConfiguration implements ILifeCycle { async onObjectCreated(ins: any, options: ObjectCreatedOptions): Promise<void> { // ... } }这里入参有两个参数:
ins当前通过构建器创出来的对象options一些参数
参数如下:
| 属性 | 类型 | 描述 |
|---|---|---|
| options.context | IMidwayContainer | 依赖注入容器本身 |
| options.definition | IObjectDefinition | 对象定义 |
| options.replaceCallback | (ins: any) => void | 对象替换的回调方法 |
示例:动态添加属性
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer, ObjectInitOptions } from '@midwayjs/core'; @Configuration() export class MainConfiguration implements ILifeCycle { async onObjectCreated(ins: any, options: ObjectInitOptions): Promise<void> { // 每个创建的对象都会添加一个 _name 的属性 ins._name = 'xxxx'; // ... } }示例:替换对象
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer, ObjectInitOptions } from '@midwayjs/core'; @Configuration() export class MainConfiguration implements ILifeCycle { async onObjectCreated(ins: any, options: ObjectInitOptions): Promise<void> { // 之后每个创建的对象都会被替换为 { bbb: 'aaa' } options.replaceCallback({ bbb: 'aaa' }); // ... } }第二个示例中,replaceCallback是对象替换的关键:一旦调用,容器后续向消费者提供的将是替换后的新对象。这种机制常被用于 AOP、代理包装等场景,但因为影响面是全局的,务必确认替换逻辑对所有业务对象都安全。
onObjectInit:异步初始化完成后
在对象实例创建后执行异步初始化方法后执行(即在对象执行完@Init()标记的初始化方法之后触发)。
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer, ObjectInitOptions } from '@midwayjs/core'; @Configuration() export class MainConfiguration implements ILifeCycle { async onObjectInit(ins: any, options: ObjectInitOptions): Promise<void> { // ... } }这里入参有两个参数:
ins当前通过构建器创出来的对象options一些参数
参数如下:
| 属性 | 类型 | 描述 |
|---|---|---|
| options.context | IMidwayContainer | 依赖注入容器本身 |
| options.definition | IObjectDefinition | 对象定义 |
:::info 在这个阶段也可以动态给对象附加属性、方法等,和onObjectCreated的区别是,这个阶段是在初始化方法执行之后。 :::
onBeforeObjectDestroy:对象销毁前
在对象实例销毁前执行,适合做对象级别的资源清理。
// src/configuration.ts import { Configuration, ILifeCycle, IMidwayContainer, ObjectBeforeDestroyOptions } from '@midwayjs/core'; @Configuration() export class MainConfiguration implements ILifeCycle { async onBeforeObjectDestroy(ins: any, options: ObjectBeforeDestroyOptions): Promise<void> { // ... } }这里入参有两个参数:
ins当前通过构建器创出来的对象options一些参数
参数如下:
| 属性 | 类型 | 描述 |
|---|---|---|
| options.context | IMidwayContainer | 依赖注入容器本身 |
| options.definition | IObjectDefinition | 对象定义 |
超时机制
从 v4 开始,框架内置了超时机制,防止某些生命周期函数阻塞应用启动。每个生命周期钩子都会在设定的时间内执行,超时后由框架中断并记录,避免单个卡死的初始化逻辑拖垮整个应用的启动/停止流程。
默认超时时间
不同生命周期方法有不同的默认超时时间:
| 生命周期方法 | 默认超时时间 | 配置项 | 说明 |
|---|---|---|---|
onConfigLoad | 10 秒 | core.configLoadTimeout | 配置加载阶段 |
onReady | 30 秒 | core.readyTimeout | 容器准备阶段 |
onServerReady | 30 秒 | core.serverReadyTimeout | 服务启动阶段 |
onStop | 默认无限制 | core.stopTimeout | 应用停止阶段 |
onHealthCheck | 1 秒 | core.healthCheckTimeout | 健康检查阶段 |
这些默认值与仓库 config.default.ts 中的实际配置一一对应:healthCheckTimeout: 1_000、configLoadTimeout: 10_000、readyTimeout: 30_000、serverReadyTimeout: 30_000。可以看到stopTimeout并未设置默认值,因此在未配置时onStop不设超时上限;如果希望停止阶段也有保护,可以主动配置该项。
自定义超时时间
可以通过配置修改每个生命周期的超时时间:
:::tip 注意,这个配置是全局的。 :::
// src/config/config.default.ts export default { core: { // 配置加载超时(毫秒) configLoadTimeout: 15_000, // 15秒 } } as MidwayConfig;单位是毫秒。同理可配置core.readyTimeout、core.serverReadyTimeout、core.healthCheckTimeout与core.stopTimeout。这些值在运行时由MidwayLifeCycleService通过configService.getConfiguration('core.xxxTimeout')读取(见 lifeCycleService.ts),并配合createPromiseTimeoutInvokeChain实现超时中断与错误记录。
在生命周期中处理超时
在生命周期方法中,可以通过第三个入参获取到当前的超时配置,以及中断信号,从而在超时前主动感知并做兜底处理:
@Configuration() export class MainConfiguration implements ILifeCycle { async onReady(container: IMidwayContainer, app: IMidwayApplication, options: { timeout?: number; abortController?: AbortController; }): Promise<void> { // 可以获取到超时配置 console.log('当前超时配置:', options.timeout); // 30000 // 可以监听中断信号 if (options.abortController) { options.abortController.signal.addEventListener('abort', () => { console.log('生命周期被中断'); }); } // 执行初始化逻辑 await this.initializeServices(); } }options.timeout即当前生命周期方法对应的超时毫秒数(如onReady默认 30000);options.abortController则携带中断信号。当执行超过时限时,框架会触发abort,你可以在监听回调中做日志记录、资源释放等收尾工作。这在初始化依赖外部服务、网络不确定的场景下尤为重要——即使初始化被中断,也能保证已占用的资源不被泄漏。
小结与推荐实践
结合仓库源码与文档,可以总结出几条实战建议:
- 初始化时机选择:修改/补充配置用
onConfigLoad;依赖注入容器内的初始化(连数据库、注册对象、加中间件)用onReady;需要拿 server、端口时用onServerReady;关闭连接、释放资源用onStop。 - 健康检查要轻量:
onHealthCheck会被健康检查 API 频繁调用且默认超时仅 1 秒,务必保持探测逻辑高效,并对超时与异常做好兜底,返回合法的HealthResult(必须含status字段)。 - 对象生命周期慎用:
onObjectCreated、onObjectInit等钩子作用于容器内所有对象,适合做全局属性注入、代理替换,但要评估对业务与框架内部对象的影响。 - 善用超时保护:为长耗时的初始化配置合理的
core.*Timeout,并在生命周期内监听abortController信号完成资源释放,避免启动被阻塞或资源泄漏。
如需继续深入,可在仓库中查看生命周期服务的完整实现 lifeCycleService.ts、健康检查服务 healthService.ts、生命周期接口定义 interface.ts 以及对应的测试夹具 base-app-object-lifecycle/src/configuration.ts 与 app-with-health-check/src/configuration.ts。
- 后端
- 微服务
- 云原生
【免费下载链接】midway
🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈
相关推荐
Chainlit 生命周期钩子:应用启动与关闭事件处理
Chainlit 生命周期钩子:应用启动与关闭事件处理 你是否在开发 Python LLM 应用时遇到过资源管理难题?启动时需要初始化模型,关闭时必须释放连接,
人工智能大模型AI 应用后端前端python-diskcache核心API详解:Cache、FanoutCache和DjangoCache的完整使用教程
python diskcache核心API详解:Cache、FanoutCache和DjangoCache的完整使用教程 python diskcache是一个
后端ttf-parser实战:构建自定义字体渲染引擎的10个步骤
ttf parser实战:构建自定义字体渲染引擎的10个步骤 ttf parser是一个高级、安全且零分配的TrueType字体解析器,它为开发者提供了强大的字
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考