用途
Metro DI 是面向 Kotlin Multiplatform(KMP)的编译期依赖注入(DI)框架,构建在 KSP2 / Kotlin 编译器插件之上,官方称已在 Cash App 生产环境验证。它通过注解在编译期生成依赖图,用于在 KMP 项目中统一管理依赖的创建、绑定与作用域。
版本与运行要求
- Metro:1.0.0(首个稳定版,2026-04-27 发布;0.x 为预稳定版,1.0 冻结了公共 API)。
- Kotlin:最低 2.2,推荐 2.3.21。
- Gradle:9.0+。
- JVM:21+。
注解历史:@DefaultBinding 自 Metro 0.13.0 起提供(并非 0.5.0);@BindingContainer 在 1.0 统一命名;@GraphExtension 在 1.0 正式化,旧的 @ScopedGraph 已移除。
支持的 KMP 目标
技能名称中的“mobile”只是主要用例,Metro 1.0 支持完整 KMP 目标矩阵:
- JVM / Android:支持,主要路径。
- iOS / macOS / watchOS / tvOS:支持,使用原生编译器插件;已移除废弃的 macosX64、tvosX64、watchosX64。
- Linux / Windows(linuxX64、mingwX64):支持。
- js(IR):支持,但在 Kotlin/JS 增量编译下,编译器插件生成顶层声明时存在已知限制,上游示例 integration-tests 中包含变通方案。
- wasmJs、wasmWasi:支持,可参考上游仓库 samples/circuit-app/src/wasmJsMain/... 中的真实 wasmJs 依赖图。
因此,即使 api 模块会被 Web 端消费,也不应跳过对 api 类型的 @Inject / @DefaultBinding;只需对 api 模块应用 Metro Gradle 插件并正常注解即可。
JS / WASM 注意事项
- React/Vue 应用中 createGraph<T>() 的生命周期:Web 端没有平台宿主生命周期,可用两种模式——顶层 lazy(图随 JS 进程存活,适合单入口 SPA)或 React Context Provider(适合多页面应用与测试)。Metro 对同一 createGraph<T> 调用返回同一底层对象,引用稳定,可经 React props 传递或存入 useRef/useMemo。
- Kotlin/JS 增量编译:当 Metro 插件经 KSP 生成的顶层声明与 kotlin.incremental.js.ir=true 冲突时,会出现“duplicate declaration”或“unresolved reference to generated symbol”错误(仅在 :jsBrowserDevelopmentRun 触发)。变通方案:对该模块设置 kotlin.incremental.js.ir=false,或在 gradle.properties 中将该属性限定到非 JS 目标。
- js(IR) 上的 object 与 class @BindingContainer:两者编译与解析行为一致,Metro 1.0 视为等价,按宿主项目惯例选择即可;object 是常见 KMP 约定,上游 samples/circuit-app/wasmJsMain 使用 object。
安装与配置
build.gradle.kts:
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.metro)
}
libs.versions.toml:
[versions]
metro = "1.0.0"
[plugins]
metro = { id = "dev.zacsweers.metro", version.ref = "metro" }
核心概念
- @DependencyGraph:依赖的根容器,每个应用入口一个。可暴露依赖属性,也可提供带运行时参数的工厂方法。通过 createGraph<AppGraph>() 创建实例。
- @Provides:定义实例的创建方式,可组合 HttpClient、ApiService、AuthRepository 等。
- @Inject:类的构造注入,Metro 可据此自动创建实现。
- @BindingContainer:把相关 provider 分组为模块,便于跨模块组织。
- 平台特定图:在 commonMain 定义通用模块,在 androidMain / iosMain 定义平台模块,再通过 @DependencyGraph(bindingContainers = [...]) 组装。
- 多模块 DI:各 feature 模块(如 AuthModule、HomeModule)以 @BindingContainer 提供绑定,在应用图中统一装配。
高级特性
- 作用域:@DependencyGraph(scope = "app", additionalScopes = ["activity"]),配合 @Scope("app") / @Scope("activity")。
- Assisted Injection:带运行时参数的类使用 @AssistedInject(而非 @Inject)与 @Assisted 参数,并定义 @AssistedFactory 工厂接口。
- Lazy 与 Provider:Lazy<AppDatabase> 首次访问时初始化;Provider<User> 每次调用返回新实例。
- Multibindings:@Multibinds 声明 Set/Map,@ContributesIntoSet / @ContributesIntoMap 贡献元素;自 0.6.0 起 multibindings 默认非空,可为空时需 allowEmpty = true。
- Graph Extensions(子组件):Metro 1.0 中,子图仅用 @GraphExtension 注解接口,嵌套工厂用 @GraphExtension.Factory;不要与 @DependencyGraph 重复注解。根图保留 @DependencyGraph(如有工厂再加 @DependencyGraph.Factory)。该机制取代了旧的 @Extends/isExtendable(自 0.4.0 起)。
- @DefaultBinding:为接口声明默认实现(自 Metro 0.13.0)。放置规则:注解写在实现类上,参数为接口,如 @DefaultBinding(AuthRepository::class);不要写在接口上并用 boundType 指向实现,该方向在 Metro API 中不存在。
- @GraphPrivate:将绑定标记为图私有,不被扩展图继承。
- Decompose 集成:组件可用 @AssistedInject 注入仓库并接收 @Assisted componentContext,配合 @AssistedFactory 创建;根组件工厂同样以 @AssistedInject 注入各子组件工厂并接收 componentContext。
适用场景
- 多模块 KMP 项目的编译期依赖装配。
- 需要跨 Android、iOS、桌面、Web(js/wasm)共享 DI 配置的项目。
- 需要作用域、Assisted Injection、Multibindings、子图扩展等较完整 DI 能力的项目。
- 与 Decompose 等组件化导航方案配合使用。
限制
- 版本要求较新:Kotlin 2.2+、Gradle 9.0+、JVM 21+。
- js(IR) 在 Kotlin/JS 增量编译下存在已知限制,需要变通配置。
- 关于收费、价格、平台支持范围之外的具体效果,资料暂未提供。