关键在于将上下文作为可预测、可管理、可测试的依赖传递通道:只挂载请求级依赖(如带request id的日志器),全局依赖(如容器、配置)应在初始化阶段注入;注入须早于业务逻辑执行,并通过类型接口、命名空间隔离和显式注入封装保障安全与可追溯。

模块执行上下文实现依赖注入,关键不是“把东西塞进上下文”,而是让上下文成为可预测、可管理、可测试的依赖传递通道。它不替代容器,但能轻量级支撑解耦——尤其适合中间件、插件系统或无框架环境。
明确上下文的职责边界
执行上下文(如 Koa 的 ctx、React 的 props 或自定义 context 对象)只应承载**请求级或调用链级**的依赖,而非全局单例本身。例如:
- 数据库连接实例(每次请求新建或从池中取)、日志器实例(带 request ID)、当前用户信息 —— 这些适合挂载到上下文
- 配置对象、服务工厂、IOC 容器实例 —— 应作为上下文的“源头”,在初始化阶段注入,而非每次请求都重建
- 避免把整个 Service 类直接挂到 ctx 上;更合理的是挂一个已初始化、带上下文感知能力的服务代理(如
ctx.userService.findById())
注入时机必须早于业务逻辑执行
依赖必须在第一个业务中间件/处理器运行前就就位,否则会出现 Cannot read property 'xxx' of undefined。典型做法是:
- Koa:用前置中间件统一挂载,且确保它 排在所有路由和业务中间件之前
- 自定义模块系统:在模块
init()或setup(context)阶段完成依赖绑定,而不是在handle()里临时判断 - React:通过 Context.Provider 包裹组件树,确保子组件渲染时 context 值已就绪;避免在 useEffect 中才初始化 provider value
保持类型安全与可追溯性
执行上下文容易变成“任意属性筐”,导致 IDE 无法提示、重构困难、运行时报错滞后。建议:
- 为上下文对象定义显式接口(TypeScript 中的
interface Context),声明db: DatabaseClient、logger: RequestLogger等字段 - 使用装饰器或包装函数封装注入逻辑,例如:
withDependencies(handler, { db, logger }),让依赖来源一目了然 - 记录依赖注入路径(如日志中打上
[DI] injected db into ctx for /api/users),便于排查漏注入或覆盖问题
避免隐式覆盖与生命周期错配
多个中间件或模块往同一上下文属性写值,极易引发覆盖冲突。例如:
- 中间件 A 设置
ctx.user = auth.verify(token),中间件 B 又误设ctx.user = {},后续逻辑就拿不到认证信息 - 将 long-lived service(如 RedisClient)挂到请求级 ctx 上,却未做连接复用或清理,造成资源泄漏
- 解决方案:用命名空间隔离(
ctx.state.auth.user、ctx.state.db),或约定只读字段 + 显式 setter 方法











