Skip to content

生命周期守卫

InferDI 提供三种生命周期:

种类创建时机缓存于由容器释放
singleton每个拥有它的容器创建一次拥有它的容器
scoped每个子作用域创建一次子作用域
transient每次解析都创建从不缓存

strict: true 模式下,从根容器解析 scoped 键会抛出 Scoped "key" cannot be resolved from the root container. Use createScope().。请从 createScope() 返回的子容器解析 scoped 服务。

生命周期规则

单例不能直接依赖 scopedtransient 服务。单例只创建一次并在每个请求间共享,因此如果它捕获了一个作用域级的值——当前请求的上下文、用户或事务——那么这一个请求的状态就会悄无声息地泄漏到所有其他请求中。InferDI 让这种边界情况在类型系统中无法被表达出来,而不是把它留给代码评审。

ts
new Container()
  .registerClass('request', RequestContext, [], 'scoped')
  .registerClass('users', UserService, ['request'], 'singleton')

该注册会被 TypeScript 拒绝。在严格模式下,如果有类型转换绕过了类型系统,同样的形态在运行时也会被拒绝。

严格模式

strict: true 是默认值。它能捕捉:

  • 直接从根容器解析 scoped 键
  • 由类型转换引入的单例到作用域级或单例到瞬态的违规
  • 捕获外层容器的工厂泄漏
  • 同步单例循环
  • 同步瞬态循环
  • 绕过静态检查的动态键误用
ts
const root = new Container({ strict: true })

快速模式

仅在测试已证明依赖图形态之后才使用 strict: false

ts
const root = new Container({ strict: false })

快速模式会从解析路径中移除运行时的循环与生命周期记账。scope 会直接读取不可变的根注册表,不再遍历父链,并把委托解析的 singleton 镜像到 scope 缓存中。strict scope 在每次本地未命中时遍历精确的父链,因此无需保留查找元数据也能立即看到依赖树变更。owned 实例的身份去重在两种模式下都于 disposal 阶段执行。它不会改变类型层面的契约,但也无法防御不诚实的类型转换、捕获的外层容器或循环。它也会跳过根容器的 scoped 保护,因此应用代码不得从根容器解析 scoped 键。

推荐的工作流程:在严格模式下开发和测试,通过单一线性 fluent 链对每个运行时键只注册一次,并在首次解析或创建 scope 前完成注册。然后只为经过审计且不可变的生产依赖图切换模式,并先释放子 scope,再释放其祖先。