☰
Midway 生命周期指南:从配置加载到优雅关闭的完整实践
2026/10/8 1:20:00 网站建设 项目流程
  • 后端
  • 微服务
  • 云原生

【免费下载链接】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. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载

在 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 中可以清晰看到完整执行链:

  1. 扫描所有@Configuration()修饰的类,创建实例并收集为生命周期实例列表;
  2. 并行绑定四类对象生命周期钩子(onBeforeObjectCreated/onObjectCreated/onObjectInit/onBeforeObjectDestroy);
  3. 依次执行所有配置类的onConfigLoad(),返回的数据通过configService.addObject()合并进配置;
  4. 依次执行onReady();
  5. 调用frameworkService.runFramework()启动框架(此时监听端口、启动 server);
  6. 依次执行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.contextIMidwayContainer依赖注入容器本身
options.definitionIObjectDefinition对象定义
options.constructorArgsany[]构造器入参

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.contextIMidwayContainer依赖注入容器本身
options.definitionIObjectDefinition对象定义
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.contextIMidwayContainer依赖注入容器本身
options.definitionIObjectDefinition对象定义

:::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.contextIMidwayContainer依赖注入容器本身
options.definitionIObjectDefinition对象定义

超时机制

从 v4 开始,框架内置了超时机制,防止某些生命周期函数阻塞应用启动。每个生命周期钩子都会在设定的时间内执行,超时后由框架中断并记录,避免单个卡死的初始化逻辑拖垮整个应用的启动/停止流程。

默认超时时间

不同生命周期方法有不同的默认超时时间:

生命周期方法默认超时时间配置项说明
onConfigLoad10 秒core.configLoadTimeout配置加载阶段
onReady30 秒core.readyTimeout容器准备阶段
onServerReady30 秒core.serverReadyTimeout服务启动阶段
onStop默认无限制core.stopTimeout应用停止阶段
onHealthCheck1 秒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,你可以在监听回调中做日志记录、资源释放等收尾工作。这在初始化依赖外部服务、网络不确定的场景下尤为重要——即使初始化被中断,也能保证已占用的资源不被泄漏。

小结与推荐实践

结合仓库源码与文档,可以总结出几条实战建议:

  1. 初始化时机选择:修改/补充配置用onConfigLoad;依赖注入容器内的初始化(连数据库、注册对象、加中间件)用onReady;需要拿 server、端口时用onServerReady;关闭连接、释放资源用onStop。
  2. 健康检查要轻量:onHealthCheck会被健康检查 API 频繁调用且默认超时仅 1 秒,务必保持探测逻辑高效,并对超时与异常做好兜底,返回合法的HealthResult(必须含status字段)。
  3. 对象生命周期慎用:onObjectCreated、onObjectInit等钩子作用于容器内所有对象,适合做全局属性注入、代理替换,但要评估对业务与框架内部对象的影响。
  4. 善用超时保护:为长耗时的初始化配置合理的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. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载
上一篇:AutoTrain Advanced图像分割评估指标:mIoU与Dice系数详解
下一篇:Markdown 新标签页插件使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询